【问题标题】:GetWorkItemAsync method exception when called from multiple threads C#.Net从多个线程 C#.Net 调用时 GetWorkItemAsync 方法异常
【发布时间】:2018-11-10 00:50:23
【问题描述】:

我正在尝试从 VSO 获取工作项的链接,当从单个线程调用时,代码似乎工作正常,但在从每个循环的并行调用时抛出异常。

我在类的构造函数中初始化了我的 vso 客户端对象:

vso = new WorkItemReporting(Config.VSTSAccessToken);

然后在一个方法中:

Parallel.ForEach(msrcAbBugsToProcess, new ParallelOptions { MaxDegreeOfParallelism = 10 }, bugId =>
{
    var workItemLinks = vso.GetWorkItemSourceCodeLinks(bugId);
});

下面的 witclient 是一个 WorkItemTrackingHttpClient,它位于不同的类 (WorkItemReporting) 中并调用 API。正是这个调用失败了。

public List<string> GetWorkItemSourceCodeLinks(int bugId)
{
    var workItemSourceCodeLinks = new List<string>();         

    var workItem = _witClient.GetWorkItemAsync(bugId, null, null, WorkItemExpand.Relations).Result;
    if (workItem?.Relations != null)
    {
        var validSourceCodeLinkTypes = new List<string> { "ArtifactLink", "Hyperlink" };
        foreach (var relation in workItem.Relations)
        {
            if (validSourceCodeLinkTypes.Contains(relation.Rel))
            {
                workItemSourceCodeLinks.Add(relation.Url);
            }
        }
    }
}   

如果我不使用 Parallel.ForEach 并且我从 API 获取所需的数据,这会很好。当我这样做时,我有 50% 的时间会收到此异常:

Object reference not set to an instance of an object.   
at System.Security.Cryptography.X509Certificates.X509CertificateCollection.GetHashCode()
   at System.Net.HttpWebRequest.GetConnectionGroupLine()
   at System.Net.HttpWebRequest.SubmitRequest(ServicePoint servicePoint)
   at System.Net.HttpWebRequest.BeginGetResponse(AsyncCallback callback, Object state)
   at System.Net.Http.HttpClientHandler.StartGettingResponse(RequestState state)
   at System.Net.Http.HttpClientHandler.StartRequest(Object obj)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   at Microsoft.VisualStudio.Services.Common.VssHttpMessageHandler.<SendAsync>d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.VisualStudio.Services.Common.VssHttpRetryMessageHandler.<SendAsync>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.VisualStudio.Services.WebApi.VssHttpClientBase.<SendAsync>d__48.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.VisualStudio.Services.WebApi.VssHttpClientBase.<SendAsync>d__45`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.VisualStudio.Services.WebApi.VssHttpClientBase.<SendAsync>d__27`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.VisualStudio.Services.WebApi.VssHttpClientBase.<SendAsync>d__26`1.MoveNext()

是不是我做错了什么?

【问题讨论】:

  • 你能分享你的 WorkItem 对象吗?

标签: c# .net azure-devops azure-devops-rest-api


【解决方案1】:

一种解决方案是在Parallel.ForEach 内部创建WorkItemReporting

Parallel.ForEach(msrcAbBugsToProcess, new ParallelOptions { MaxDegreeOfParallelism = 10 }, bugId =>
{
    var vso = new WorkItemReporting(Config.VSTSAccessToken);
    var workItemLinks = vso.GetWorkItemSourceCodeLinks(bugId);
});

【讨论】:

  • 这有什么帮助?
  • @PrashanthSubramanian 它通过为WorkItemReporting 中的每个线程使用唯一的_witClient 来提供帮助
  • 知道了,这似乎可行,这是否意味着不能在 c# 中使用同一对象从多个线程调用方法?还是 API 无法处理的特定问题?
  • 只是猜测,但似乎_witClientHttpClient 的包装器,它不是线程安全的。
  • 根据@AlexSikilinda 在使用多线程时的评论,这都是并行的,你必须使用其他方法来“锁定”资源访问,这样一次只有1个线程访问线程外的资源,否则您会遇到资源争用问题。
【解决方案2】:

您需要通过执行以下操作来锁定对外部资源的访问。

根据评论@AlexSikilinda _witClient 一定不是线程安全的。

对于你所做的事情,仅仅因为你可以创建多个线程并不意味着你应该,我会得到时间,关于你在代码中使用和不使用并行处理的情况,

请记住,如果您使用非线程安全的外部资源并锁定,那么它可能与在正常的 for 循环中运行一样慢。您需要测试这两种情况。

在您的方法之外添加一个对象以允许从每个线程访问锁定对象,这将有助于管理对非线程安全类的访问。

vso = new WorkItemReporting(Config.VSTSAccessToken);
private readonly object _witLock = new object();

在下文中,您不想像我一样锁定整个方法内容,否则您肯定会在这段代码中使用 Parallel 浪费时间。

根据您的想法,这应该会阻止异常。

您需要重构代码,以便只有对 _witClient 的调用在锁内。因此,只需使用正确的类型在锁外声明 workItem 变量,然后仅包装对 workItem = _witClient.GetWorkItemAsync(bugId, null, null, WorkItemExpand.Relations).Result; 的调用在您的锁码中。

public List<string> GetWorkItemSourceCodeLinks(int bugId)
{
    var workItemSourceCodeLinks = new List<string>();         
    lock (_witLock)
    {
        var workItem = _witClient.GetWorkItemAsync(bugId, null, null, WorkItemExpand.Relations).Result;
        if (workItem?.Relations != null)
        {
            var validSourceCodeLinkTypes = new List<string> { "ArtifactLink", "Hyperlink" };
            foreach (var relation in workItem.Relations)
            {
                if (validSourceCodeLinkTypes.Contains(relation.Rel))
                {
                    workItemSourceCodeLinks.Add(relation.Url);
                }
            }
        }
    }
}  

祝你好运

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多