SPListItemCollection 上的 C# Parallel.ForEach() 导致异常 (0x80010102)

C# Parallel.ForEach() on SPListItemCollection causes exception (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 Class 构造函数:

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 the SPObjectWithHistory 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),每个批次执行我原来的 post 中的代码(使用 SPQuery.Query 每批次都有变化)。之后,我的 GetSPObjectsWithHistory 的结果被合并到一个列表中。