Ответ 1
Это освежает, чтобы увидеть кого-то, кто сделал домашнее задание.
Прежде всего, с .NET 4 (и это все еще очень актуально) TAP является предпочтительной технологией для async workflow в .NET. Задачи легко скомпонованы, и для вас параллелизировать вызовы веб-сервисов - это легкий ветерок, если они предоставляют истинные Task<T>
-отверждающиеся API. На данный момент вы "подделали" его с помощью Task.Run
, и пока это может быть достаточно для ваших целей. Конечно, ваши потоки потоков потоков будут тратить много времени на блокировку, но если загрузка сервера не очень высока, вы можете с ней справиться, даже если это не идеальная вещь.
Вам просто нужно исправить потенциальное состояние гонки в вашем коде (подробнее об этом ближе к концу).
Если вы хотите следовать лучшим практикам, вы идете с истинным TAP. Если ваши API предоставляют Task
-отверждающиеся методы из коробки, это легко. Если нет, это не игра, так как APM и EAP могут быть легко преобразованы в TAP. Ссылка MSDN: https://msdn.microsoft.com/en-us/library/hh873178(v=vs.110).aspx
Здесь также будут приведены примеры конверсий.
APM (взято из другого вопроса SO):
MessageQueue
не предоставляет метод ReceiveAsync
, но мы можем заставить его играть мяч через Task.Factory.FromAsync
:
public static Task<Message> ReceiveAsync(this MessageQueue messageQueue)
{
return Task.Factory.FromAsync(messageQueue.BeginReceive(), messageQueue.EndPeek);
}
...
Message message = await messageQueue.ReceiveAsync().ConfigureAwait(false);
Если у ваших прокси-серверов веб-служб есть методы BeginXXX
/EndXXX
, это путь.
EAP
Предположим, у вас есть старый прокси веб-службы, полученный из SoapHttpClientProtocol
, с использованием только методов async на основе событий. Вы можете преобразовать их в TAP следующим образом:
public Task<long> GetPointAsyncTask(this PointWebService webService, int nationalCode)
{
TaskCompletionSource<long> tcs = new TaskCompletionSource<long>();
webService.GetPointAsyncCompleted += (s, e) =>
{
if (e.Cancelled)
{
tcs.SetCanceled();
}
else if (e.Error != null)
{
tcs.SetException(e.Error);
}
else
{
tcs.SetResult(e.Result);
}
};
webService.GetPointAsync(nationalCode);
return tcs.Task;
}
...
using (PointWebService service = new PointWebService())
{
long point = await service.GetPointAsyncTask(123).ConfigureAwait(false);
}
Избегайте гонок при агрегировании результатов
Что касается объединения параллельных результатов, то ваш код цикла TAP почти прав, но вам нужно избегать мутирования общего состояния внутри ваших тел Task
, поскольку они, скорее всего, будут выполняться параллельно. Общее состояние Result
в вашем случае - это какая-то коллекция. Если эта коллекция не является потокобезопасной (т.е. Если она простая List<long>
), то у вас есть условие гонки, и вы можете получить исключения и/или отбросить результаты на Add
(я предполагаю AddRange
в вашем коде была опечаткой, но если нет - выше все еще применяется).
Простой асинхронный переписать, который фиксирует вашу расу, будет выглядеть так:
List<Task<long>> tasks = new List<Task<long>>();
foreach (BaseClass item in Clients) {
tasks.Add(item.GetPointAsync(MasterLogId, mobileNumber));
}
long[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
Если вы решили быть ленивым и придерживаться решения Task.Run
на данный момент, исправленная версия будет выглядеть так:
List<Task<long>> tasks = new List<Task<long>>();
foreach (BaseClass item in Clients)
{
Task<long> dodgyThreadPoolTask = Task.Run(
() => item.GetPoint(MasterLogId, mobileNumber)
);
tasks.Add(dodgyThreadPoolTask);
}
long[] results = await Task.WhenAll(tasks).ConfigureAwait(false);