【问题标题】:C# Parallel.ForEach() on SPListItemCollection causes exception (0x80010102)SPListItemCollection 上的 C# Parallel.ForEach() 导致异常 (0x80010102)
【发布时间】:2018-06-23 23:11:38
【问题描述】:

在我的 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(Boolean includeTaskCanceledExceptions)

在 System.Threading.Tasks.Task.Wait(Int32 毫秒超时, CancellationToken cancelToken)

在 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 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 源,Action'1 主体)

在 GetSPObjectsWithHistory(SPQuery query, List`1 filters) in...

内部异常:

消息:尝试在单线程模式下调用多个线程。 (来自 HRESULT 的异常:0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))

类型: Microsoft.SharePoint.SPException

堆栈跟踪:

在 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)

SPObjectWithHistory 构造函数中的double.TryParse(spItem.Versions[0].VersionLabel, out _currentVersion); 行。

InnerException

消息:尝试在单线程模式下调用多个线程。 (来自 HRESULT 的异常:0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))

类型: System.Runtime.InteropServices.COMException

堆栈跟踪:

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

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

会有人知道如何让我的代码工作吗?

提前致谢!

【问题讨论】:

  • 听起来 API 在输出到 COM 时不是线程安全的。只是不要在它上面做多个线程。
  • 无论如何,你并没有真正做太多的事情来提高性能。
  • 我认为如果我可以使用Parallel.ForEach,它实际上会快很多,因为应用程序会同时执行resultsList.Add(new SPObjectWithHistory&lt;T&gt;(item, filters));(+-0.3 秒)多次。在 80000 个项目的集合中,这将产生相当大的影响。即使在 2 个并行线程而不是一个线程上执行它也会产生巨大的差异。
  • COM 是单线程的,除非代码明确指出,以避免多线程固有的所有问题。这个组件并不表示它对线程是安全的,所以它对线程是不安全的,不管你多么想要它。考虑使用延迟加载——例如,是否真的需要预先检索该列表项的所有 80,000 项?什么用户会浏览它?即使您需要自定义对象,您也可以将必要的推荐数据存储在自定义集合中,并按需实现/检索这些数据。
  • @JeroenMostert 感谢您的澄清评论。不幸的是,就我而言,延迟加载不是一种选择。看来我将不得不采取不同的方法。

标签: c# multithreading asp.net-mvc-5 sharepoint-2013 splistitem


【解决方案1】:

显然,我试图做的事情是不可能的。 Microsoft.SharePoint 命名空间的 SP 对象不是线程安全的,就像 @JeroenMostert 所说的那样。

除非代码明确指出,否则 COM 是单线程的, 避免多线程固有的所有问题。这个组件 并不表示它对线程是安全的,所以它对线程不安全 线程,无论你想要多少。考虑使用惰性 加载——真的有必要检索所有 80,000 项 例如,前面的列表项?什么用户会浏览它?甚至 如果你想要自定义对象,你可以存储必要的推荐 自定义集合中的数据并按需实现/检索这些数据。

由于延迟加载对我来说不是一个选项,因此我决定将我的逻辑分成多个批次(使用 System.Threading.Task),每个批次都执行我原始帖子中的代码(SPQuery.Query 每批次都在更改)。之后,我的GetSPObjectsWithHistory 的结果将合并到一个列表中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-18
    • 2011-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多