c#async / await機能を使用せずに、ブロックせずに非同期操作をループするための最良の方法は何ですか?
たとえば、forループ内でURLのHTMLのリストを非同期的にダウンロードします。
私はしばらくの間ループに陥り続け、TPLはさらに作業がある場合は自分自身を呼び出し続けます。しかし、もっと良い方法はありますか?
c#async / await機能を使用せずに、ブロックせずに非同期操作をループするための最良の方法は何ですか?
たとえば、forループ内でURLのHTMLのリストを非同期的にダウンロードします。
私はしばらくの間ループに陥り続け、TPLはさらに作業がある場合は自分自身を呼び出し続けます。しかし、もっと良い方法はありますか?
あなたが説明するパターンは悪くはありませんが、それをヘルパーメソッドに抽象化することができます。ForEachAsync
またはのようなものConvertAllAsync
。これにより、コードからループが削除されます。これにより、本質的でない複雑さが最小限に抑えられます。
これがの実装ですForEachAsync
:
public static Task ForEachAsync<T>(this TaskFactory factory, IEnumerable<T> items, Func<T, int, Task> getProcessItemTask)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
IEnumerator<T> enumerator = items.GetEnumerator();
int i = 0;
Action<Task> continuationAction = null;
continuationAction = ante =>
{
if (ante.IsFaulted)
tcs.SetException(ante.Exception);
else if (ante.IsCanceled)
tcs.TrySetCanceled();
else
StartNextForEachIteration(factory, tcs, getProcessItemTask, enumerator, ref i, continuationAction);
};
StartNextForEachIteration(factory, tcs, getProcessItemTask, enumerator, ref i, continuationAction);
tcs.Task.ContinueWith(_ => enumerator.Dispose(), TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
static void StartNextForEachIteration<T>(TaskFactory factory, TaskCompletionSource<object> tcs, Func<T, int, Task> getProcessItemTask, IEnumerator<T> enumerator, ref int i, Action<Task> continuationAction)
{
bool moveNext;
try
{
moveNext = enumerator.MoveNext();
}
catch (Exception ex)
{
tcs.SetException(ex);
return;
}
if (!moveNext)
{
tcs.SetResult(null);
return;
}
Task iterationTask = null;
try
{
iterationTask = getProcessItemTask(enumerator.Current, i);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
i++;
if (iterationTask != null)
iterationTask.ContinueWith(continuationAction, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, factory.Scheduler ?? TaskScheduler.Default);
}