Исключение метода GetWorkItemAsync при вызове из нескольких потоков C # .Net - PullRequest
0 голосов
/ 10 ноября 2018

Я пытаюсь получить ссылки от VSO для рабочих элементов, и кажется, что код работает нормально при вызове из одного потока, но выдает исключения при вызове из параллели для каждого цикла.

Я инициализирую свой клиентский объект vso в конструкторе моего класса:

vso = new WorkItemReporting(Config.VSTSAccessToken);

Затем в методе:

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

Ниже показан 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()

Есть ли что-то, что я делаю не так?

Ответы [ 2 ]

0 голосов
/ 10 ноября 2018

Вам необходимо заблокировать доступ к внешнему ресурсу, выполнив что-то вроде следующего.

Согласно комментарию @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);
                }
            }
        }
    }
}  

Удачи

0 голосов
/ 10 ноября 2018

Решение состоит в том, чтобы создать WorkItemReporting внутри Parallel.ForEach:

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