在我的 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
处收到以下异常:
Message: One or more errors occurred.
Type: System.AggregateException
StackTrace:
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at 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)
at System.Threading.Tasks.Parallel.ForEachWorker[TSource,TLocal](IEnumerable'1 source, ParallelOptions parallelOptions, Action'1 body, Action'2 bodyWithState, Action'3 bodyWithStateAndIndex, Func'4 bodyWithStateAndLocal, Func'5 bodyWithEverything, Func'1 localInit, Action'1 localFinally)
at System.Threading.Tasks.Parallel.ForEach[TSource](IEnumerable'1 source, Action'1 body)
at GetSPObjectsWithHistory(SPQuery query, List`1 filters) in...
InnerException:
Message: Attempted to make calls on more than one thread in single threaded mode. (Exception from HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))
Type: Microsoft.SharePoint.SPException
StackTrace:
at Microsoft.SharePoint.SPGlobal.HandleComException(COMException comEx)
at Microsoft.SharePoint.Library.SPRequest.SetVar(String bstrUrl, String bstrName, String bstrValue)
at Microsoft.SharePoint.SPListItemVersionCollection.EnsureVersionsData()
at Microsoft.SharePoint.SPListItemVersionCollection.get_Item(Int32 iIndex)
at line of
double.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion);
in theSPObjectWithHistory
constructor.InnerException:
Message: Attempted to make calls on more than one thread in single threaded mode. (Exception from HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))
Type: System.Runtime.InteropServices.COMException
StackTrace:
at Microsoft.SharePoint.Library.SPRequestInternalClass.SetVar(String bstrUrl, String bstrName, String bstrValue)
at Microsoft.SharePoint.Library.SPRequest.SetVar(String bstrUrl, String bstrName, String bstrValue)
会有人知道如何让我的代码工作吗?
提前致谢!
最佳答案
显然,我试图做的事情是不可能的。 Microsoft.SharePoint
命名空间的 SP 对象不是线程安全的,如@JeroenMostert 所述。
COM is single threaded unless the code explicitly indicates otherwise, to avoid all the problems inherent with multithreading. This component does not indicate it's safe for threading, so it's not safe for threading, no matter how much you want it to be. Consider using lazy loading -- is it really necessary to retrieve all 80,000 items of that list item up front, for example? What user will browse that? Even if you want custom objects, you could store the necessary referral data in a custom collection and materialize/retrieve these on demand.
由于延迟加载对我来说不是一个选项,我决定将我的逻辑分成多个批处理(使用 System.Threading.Task
),每个批处理执行我原始帖子中的代码(使用 SPQuery.Query
每批更改)。之后,我的 GetSPObjectsWithHistory
的结果被合并到一个列表中。
关于SPListItemCollection 上的 C# Parallel.ForEach() 导致异常 (0x80010102),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48260998/