在我的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
堆栈跟踪:
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)
在System.Threading.Tasks.Parallel.ForEach [TSource](IEnumerable'1 source,Action'1 body)
在GetSPObjectsWithHistory(SPQuery查询,List`1过滤器)中......
的InnerException:
消息:尝试在单线程模式下对多个线程进行调用。 (来自HRESULT的异常:0x80010102(RPC_E_ATTEMPTED_MULTITHREAD))
输入: Microsoft.SharePoint.SPException
堆栈跟踪:
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)
位于
double.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion);
构造函数中的SPObjectWithHistory
的行。的InnerException :
消息:尝试在单线程模式下对多个线程进行调用。 (来自HRESULT的异常:0x80010102(RPC_E_ATTEMPTED_MULTITHREAD))
输入: 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)
是否有人知道如何让我的代码工作?
提前致谢!
答案 0 :(得分:2)
显然,我试图做的事情是不可能的。 Microsoft.SharePoint
命名空间的SP对象不是线程安全的,就像@JeroenMostert所说的那样。
COM是单线程的,除非代码明确指出, 避免多线程所固有的所有问题。这个组件 并不表示它是安全的线程,所以它是不安全的 线程,无论你想要多少。考虑使用懒惰 加载 - 是否真的需要检索所有80,000项 例如,前面的列表项目?用户将浏览哪些内容?甚至 如果您想要自定义对象,则可以存储必要的引用 自定义集合中的数据,并按需实现/检索这些数据。
由于延迟加载对我来说不是一个选项,我决定将我的逻辑分成批处理(使用System.Threading.Task
),每个都执行原始帖子中的代码(每个SPQuery.Query
更改一次批量)。之后,我GetSPObjectsWithHistory
的结果将合并到一个列表中。