рекомендуется использовать ConfigureAwait(false)
каждый раз, когда вы можете, особенно в библиотеках, потому что это может помочь избежать взаимоблокировок и повысить производительность.
Я написал библиотеку, которая сильно использует async (обращается к веб-службам для БД). Пользователи библиотеки зашли в тупик, и после очень мучительной отладки и манипуляции я отследил ее до единственного использования await Task.Yield()
. Везде, где у меня есть ожидание, я использую .ConfigureAwait(false)
, однако это не поддерживается на Task.Yield()
.
Какое рекомендуемое решение для ситуаций, когда требуется эквивалент Task.Yield().ConfigureAwait(false)
?
Я читал о том, как был удален SwitchTo
метод. Я могу понять, почему это может быть опасно, но почему нет эквивалента Task.Yield().ConfigureAwait(false)
?
Edit:
Чтобы предоставить дополнительный контекст для моего вопроса, вот какой-то код. Я реализую библиотеку с открытым исходным кодом для доступа к DynamoDB (распределенной базе данных как услуге AWS), которая поддерживает async. Ряд операций возвращает IAsyncEnumerable<T>
, как это предусмотрено IX-Async library. Эта библиотека не обеспечивает хороший способ генерации списков async из источников данных, которые предоставляют строки в "кусках", то есть каждый запрос async возвращает много элементов. Поэтому для этого у меня есть свой общий тип. Библиотека поддерживает опцию "читать вперед", позволяющую пользователю указать, сколько данных нужно запрашивать раньше, когда это действительно необходимо вызовом MoveNext()
.
В основном, как это работает, я делаю запросы на куски, вызывая GetMore()
и проходя через состояние между ними. Я помещал эти задачи в очередь chunks
и деактивировал их и превратил их в фактические результаты, которые я ввел в отдельную очередь. Метод NextChunk()
здесь. В зависимости от значения ReadAhead
я буду продолжать получать следующий фрагмент, как только последний будет выполнен (все), или нет, пока не будет необходимо, но не будет доступно (None), или только получите следующий фрагмент за пределами значений, которые в настоящее время используется (некоторые). Из-за этого получение следующего фрагмента должно выполняться параллельно/не блокировать получение следующего значения. Для этого перечисляющий код:
private class ChunkedAsyncEnumerator<TState, TResult> : IAsyncEnumerator<TResult>
{
private readonly ChunkedAsyncEnumerable<TState, TResult> enumerable;
private readonly ConcurrentQueue<Task<TState>> chunks = new ConcurrentQueue<Task<TState>>();
private readonly Queue<TResult> results = new Queue<TResult>();
private CancellationTokenSource cts = new CancellationTokenSource();
private TState lastState;
private TResult current;
private bool complete; // whether we have reached the end
public ChunkedAsyncEnumerator(ChunkedAsyncEnumerable<TState, TResult> enumerable, TState initialState)
{
this.enumerable = enumerable;
lastState = initialState;
if(enumerable.ReadAhead != ReadAhead.None)
chunks.Enqueue(NextChunk(initialState));
}
private async Task<TState> NextChunk(TState state, CancellationToken? cancellationToken = null)
{
await Task.Yield(); // ** causes deadlock
var nextState = await enumerable.GetMore(state, cancellationToken ?? cts.Token).ConfigureAwait(false);
if(enumerable.ReadAhead == ReadAhead.All && !enumerable.IsComplete(nextState))
chunks.Enqueue(NextChunk(nextState)); // This is a read ahead, so it shouldn't be tied to our token
return nextState;
}
public Task<bool> MoveNext(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if(results.Count > 0)
{
current = results.Dequeue();
return TaskConstants.True;
}
return complete ? TaskConstants.False : MoveNextAsync(cancellationToken);
}
private async Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
Task<TState> nextStateTask;
if(chunks.TryDequeue(out nextStateTask))
lastState = await nextStateTask.WithCancellation(cancellationToken).ConfigureAwait(false);
else
lastState = await NextChunk(lastState, cancellationToken).ConfigureAwait(false);
complete = enumerable.IsComplete(lastState);
foreach(var result in enumerable.GetResults(lastState))
results.Enqueue(result);
if(!complete && enumerable.ReadAhead == ReadAhead.Some)
chunks.Enqueue(NextChunk(lastState)); // This is a read ahead, so it shouldn't be tied to our token
return await MoveNext(cancellationToken).ConfigureAwait(false);
}
public TResult Current { get { return current; } }
// Dispose() implementation omitted
}
Я не утверждаю, что этот код является совершенным. Извините, что так долго, не был уверен, как упростить. Важной частью является метод NextChunk
и вызов Task.Yield()
. Эта функциональность используется с помощью статического метода построения:
internal static class AsyncEnumerableEx
{
public static IAsyncEnumerable<TResult> GenerateChunked<TState, TResult>(
TState initialState,
Func<TState, CancellationToken, Task<TState>> getMore,
Func<TState, IEnumerable<TResult>> getResults,
Func<TState, bool> isComplete,
ReadAhead readAhead = ReadAhead.None)
{ ... }
}