С# Parallel.ForEach() в SPListItemCollection вызывает исключение (0x80010102)

В моем приложении ASP.NET MVC я пытаюсь получить все элементы в списке с историей версий, а затем применить их к пользовательскому объекту. Для этого я использую Microsoft.SharePoint.

Я изначально делал это следующим образом:

Util.GetSPItemCollectionWithHistory метод:

public static SPListItemCollection GetSPItemCollectionWithHistory(string listName, SPQuery filterQuery)
{
 using (SPSite spSite = new SPSite(sp_URL))
 {
 using (SPWeb spWeb = spSite.OpenWeb())
 {
 SPList itemsList = spWeb.GetList("/Lists/" + listName);
 SPListItemCollection listItems = itemsList.GetItems(filterQuery);

 return listItems;
 }
 }
}

Метод GetSPObjectsWithHistory:

protected static List<spobjectwithhistory<t>> GetSPObjectsWithHistory(SPQuery query = null, List<string> filters = null)
{
 List<spobjectwithhistory<t>> resultsList = new List<spobjectwithhistory<t>>();

 Type objectType = typeof(T);
 string listName = "";

 query = query ?? Util.DEFAULT_SSOM_QUERY;

 if (objectType == typeof(SPProject)) listName = Util.PROJECTS_LIST_NAME;
 else if (objectType == typeof(SPTask)) listName = Util.TASKS_LIST_NAME;
 else throw new Exception(String.Format("Could not find the list name for {0} objects.", objectType.Name));

 SPListItemCollection results = Util.GetSPItemCollectionWithHistory(listName, query);
 foreach (SPListItem item in results)
 {
 resultsList.Add(new SPObjectWithHistory<t>(item, filters));
 }

 return resultsList;
}
</t></spobjectwithhistory<t></spobjectwithhistory<t></string></spobjectwithhistory<t>

Конструктор класса SPObjectWithHistory:

public SPObjectWithHistory(SPListItem spItem, List<string> filters = null)
{
 ******.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion);
 History = new Dictionary<******, t="">();

 if (spItem.Versions.Count > 1)
 {
 for (int i = 1; i < spItem.Versions.Count; i++)
 {
 if (filters == null)
 History.Add(******.Parse(spItem.Versions[i].VersionLabel), SPObject<t>.ConvertSPItemVersionObjectToSPObject(spItem.Versions[i]));
 else
 {
 foreach (string filter in filters)
 {
 if (i == spItem.Versions.Count - 1 || (string)spItem.Versions[i][filter] != (string)spItem.Versions[i + 1][filter])
 {
 History.Add(******.Parse(spItem.Versions[i].VersionLabel), SPObject<t>.ConvertSPItemVersionObjectToSPObject(spItem.Versions[i]));
 break;
 }
 }
 }
 }
 }
}
</t></t></******,></string>

Таким образом, код работает, но он очень медленный в больших списках. В одном из списков содержится более 80000 элементов, и создание одного элемента SPObjectWithHistory занимает около 0,3 секунды из-за логики в конструкторе.

Чтобы ускорить процесс, я хотел использовать Parallel.ForEach вместо обычного foreach.

GetSPObjectsWithHistory моя GetSPObjectsWithHistory обновилась:

protected static List<spobjectwithhistory<t>> GetSPObjectsWithHistory(SPQuery query = null, List<string> filters = null)
{
 ConcurrentBag<spobjectwithhistory<t>> resultsList = new ConcurrentBag<spobjectwithhistory<t>>();

 Type objectType = typeof(T);
 string listName = "";

 query = query ?? Util.DEFAULT_SSOM_QUERY;

 if (objectType == typeof(SPProject)) listName = Util.PROJECTS_LIST_NAME;
 else if (objectType == typeof(SPTask)) listName = Util.TASKS_LIST_NAME;
 else throw new Exception(String.Format("Could not find the list name for {0} objects.", objectType.Name));

 List<splistitem> results = Util.GetSPItemCollectionWithHistory(listName, query).Cast<splistitem>().ToList();
 Parallel.ForEach(results, item => resultsList.Add(new SPObjectWithHistory<t>(item, filters)));

 return resultsList.ToList();
}
</t></splistitem></splistitem></spobjectwithhistory<t></spobjectwithhistory<t></string></spobjectwithhistory<t>

Однако, когда я пытаюсь запустить приложение, я получаю следующее исключение в Parallel.ForEach:

<span>Сообщение:</span> Произошла одна или несколько ошибок.

<span>Тип:</span> System.AggregateException

<span>Трассировки стека:</span>

в System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)

в System.Threading.Tasks.Task.Wait(Int32 миллисекундыTimeout, CancellationToken cancelationToken)

в System.Threading.Tasks.Parallel.ForWorker [TLocal] (Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action'1 body, Action'2 bodyWithState, Func'4 bodyWithLocal, Func'1 localInit, Action'1 localFinally)

в System.Threading.Tasks.Parallel.ForEachWorker [TSource, TLocal] (источник IEnumerable'1, ParallelOptions parallelOptions, тело Action'1, Action'2 bodyWithState, Action'3 bodyWithStateAndIndex, Func'4 bodyWithStateAndLocal, Func'5 bodyWithEverything, Func '1 localInit, Action'1 localFinally)

в System.Threading.Tasks.Parallel.ForEach [TSource] (источник IEnumerable'1, тело Action'1)

в GetSPObjectsWithHistory (запрос SPQuery, фильтры List'1) в...

<span>InnerException:</span>

<blockquote>

<span>Сообщение:</span> Попытка совершать вызовы более чем одного потока в однопоточном режиме. (Исключение из HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))

<span>Тип:</span> Microsoft.SharePoint.SPException

<span>Трассировки стека:</span>

в Microsoft.SharePoint.SPGlobal.HandleComException(COMException comEx)

в Microsoft.SharePoint.Library.SPRequest.SetVar(String bstrUrl, String bstrName, String bstrValue)

в Microsoft.SharePoint.SPListItemVersionCollection.EnsureVersionsData()

в Microsoft.SharePoint.SPListItemVersionCollection.get_Item (Int32 iIndex)

в строке <code>******.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion);</code> в конструкторе <code>SPObjectWithHistory</code>.

<span>InnerException</span>:

<blockquote>

<span>Сообщение:</span> Попытка совершать вызовы более чем одного потока в однопоточном режиме. (Исключение из HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))

<span>Тип:</span> System.Runtime.InteropServices.COMException

<span>Трассировки стека:</span>

в Microsoft.SharePoint.Library.SPRequestInternalClass.SetVar(String bstrUrl, String bstrName, String bstrValue)

в Microsoft.SharePoint.Library.SPRequest.SetVar(String bstrUrl, String bstrName, String bstrValue)

Будет ли кто-нибудь, кто знает, как я могу заставить мой код работать?

Заранее спасибо!

1 ответ

Видимо, то, что я пытаюсь сделать, невозможно. Объекты SP пространства имен Microsoft.SharePoint не являются потокобезопасными, как указано в @*************.

COM является одиночным, если код явно не указывает иначе, чтобы избежать всех проблем, присущих многопоточности. Этот компонент не указывает на то, что он безопасен для потоковой передачи, поэтому он небезопасен для потоковой передачи, независимо от того, насколько вы хотите. Рассмотрим использование ленивой загрузки - действительно ли нужно, например, извлечь все 80 000 предметов этого списка? Какой пользователь будет просматривать это? Даже если вам нужны пользовательские объекты, вы можете сохранить необходимые реферальные данные в пользовательской коллекции и материализовать/получить их по требованию.

Поскольку ленивая загрузка не была для меня вариантом, я решил разделить свою логику на партии (используя System.Threading.Task), каждый из которых выполняет код из моего исходного сообщения (при изменении SPQuery.Query для каждой партии). После этого результаты из моей GetSPObjectsWithHistory объединяются в один список.

licensed under cc by-sa 3.0 with attribution.