C# 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;
}
SPObjectWithHistory Конструктор класса:
public SPObjectWithHistory(SPListItem spItem, List<string> filters = null)
{
double.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion);
History = new Dictionary<double, T>();
if (spItem.Versions.Count > 1)
{
for (int i = 1; i < spItem.Versions.Count; i++)
{
if (filters == null)
History.Add(double.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(double.Parse(spItem.Versions[i].VersionLabel), SPObject<T>.ConvertSPItemVersionObjectToSPObject(spItem.Versions[i]));
break;
}
}
}
}
}
}
Таким образом, код работает, но он очень медленный в больших списках. В одном из списков содержится более 80000 элементов, и создается один SPObjectWithHistory
Элемент занимает около 0,3 секунды, из-за логики в конструкторе.
Чтобы ускорить процесс, я хотел использовать Parallel.ForEach
вместо обычного foreach
,
мой 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();
}
Однако, когда я сейчас пытаюсь запустить приложение, я получаю следующее исключение на Parallel.ForEach
:
Сообщение: произошла одна или несколько ошибок.
Тип: System.AggregateException
Трассировки стека:
в System.Threading.Tasks.Task.ThrowIfExceptional(логическое значение includeTaskCanceledExceptions)
в System.Threading.Tasks.Task.Wait(Int32 миллисекунд Timeout, CancellationToken cancellationToken)
в System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 от Inclusive, Int32 до Exclusive, ParallelOptions parallelOptions, тело Action'1, тело действия Action'2 bodyWithState, тело функции Func'4 bodyWithLocal, localInit функции Func'1, localFit Actionc1)
в System.Threading.Tasks.Parallel.ForEachWorker[TSource,TLocal](источник IEnumerable'1, параллельные параметры ParallelOptions, тело Action'1, тело Action'2 bodyWithState, action'3 bodyWithStateAndIndex, Func'4 bodyWithStateAndLocal, Func'5 bodyWcE '1 localInit, Action'1 localFinally)
в System.Threading.Tasks.Parallel.ForEach[TSource](источник IEnumerable'1, тело Action'1)
в GetSPObjectsWithHistory(запрос SPQuery, фильтры List`1) в...
InnerException:
Сообщение. Попытка сделать вызов более чем в одном потоке в однопоточном режиме. (Исключение из HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))
Тип: Microsoft.SharePoint.SPException
Трассировки стека:
в Microsoft.SharePoint.SPGlobal.HandleComException(COMException comEx)
в Microsoft.SharePoint.Library.SPRequest.SetVar(строка bstrUrl, строка bstrName, строка bstrValue)
в Microsoft.SharePoint.SPListItemVersionCollection.EnsureVersionsData()
в Microsoft.SharePoint.SPListItemVersionCollection.get_Item(Int32 iIndex)
на линии
double.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion);
вSPObjectWithHistory
конструктор.InnerException:
Сообщение. Попытка сделать вызов более чем в одном потоке в однопоточном режиме. (Исключение из HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))
Тип: System.Runtime.InteropServices.COMException
Трассировки стека:
в Microsoft.SharePoint.Library.SPRequestInternalClass.SetVar(строка bstrUrl, строка bstrName, строка bstrValue)
в Microsoft.SharePoint.Library.SPRequest.SetVar(строка bstrUrl, строка bstrName, строка bstrValue)
Будет ли кто-нибудь, кто знает, как мне заставить мой код работать?
Заранее спасибо!
1 ответ
Видимо, то, что я пытался сделать, невозможно. Microsoft.SharePoint
Объекты SP пространства имен не являются потокобезопасными, как указано в @JeroenMostert.
COM является однопоточным, если в коде явно не указано иное, чтобы избежать всех проблем, присущих многопоточности. Этот компонент не означает, что он безопасен для многопоточности, поэтому он не безопасен для многопоточности, независимо от того, насколько вы этого хотите. Подумайте об использовании отложенной загрузки - действительно ли необходимо, например, получить все 80000 элементов этого элемента списка заранее? Какой пользователь будет просматривать это? Даже если вам нужны пользовательские объекты, вы можете сохранить необходимые реферальные данные в пользовательской коллекции и материализовать / получить их по требованию.
Поскольку ленивая загрузка не была для меня опцией, я решил разделить свою логику на пакеты (используя System.Threading.Task
), каждый из которых выполняет код из моего исходного поста (с SPQuery.Query
меняется для каждой партии). После этого результаты из моего GetSPObjectsWithHistory
объединены в единый список.