Task.Yield () na biblioteca precisa de ConfigureWait (false)

Estárecomendado que um usoConfigureAwait(false) sempre que puder, especialmente em bibliotecas, pois pode ajudar a evitar conflitos e melhorar o desempenho.

Eu escrevi uma biblioteca que faz uso pesado de assíncrono (acessa serviços da web para um banco de dados). Os usuários da biblioteca estavam com um impasse e, depois de muitas depurações e ajustes dolorosos, eu o localizei para o uso único deawait Task.Yield(). Em qualquer outro lugar que eu aguarde, eu uso.ConfigureAwait(false), no entanto, isso não é suportado emTask.Yield().

Qual é a solução recomendada para situações em que é necessário o equivalente aTask.Yield().ConfigureAwait(false)?

Eu li sobre como houve umaSwitchTo método que foi removido. Eu posso ver por que isso pode ser perigoso, mas por que não há equivalente aTask.Yield().ConfigureAwait(false)?

Editar:

Para fornecer um contexto adicional para minha pergunta, aqui está um código. Estou implementando umbiblioteca de código aberto para acessar o DynamoDB (um banco de dados distribuído como um serviço da AWS) compatível com assíncrono. Um número de operações retornamIAsyncEnumerable<T> conforme fornecido peloBiblioteca IX-Async. Essa biblioteca não fornece uma boa maneira de gerar enumeráveis assíncronos a partir de fontes de dados que fornecem linhas em "pedaços", ou seja, cada solicitação assíncrona retorna muitos itens. Então, eu tenho meu próprio tipo genérico para isso. A biblioteca suporta uma opção de leitura antecipada, permitindo que o usuário especifique quantos dados devem ser solicitados com antecedência quando realmente forem necessários por uma chamada paraMoveNext().

Basicamente, como isso funciona é que eu faço solicitações de partes chamandoGetMore() e passando ao longo do estado entre estes. Eu coloquei essas tarefas em umchunks enfileirar e desenfileirá-los e transformá-los em resultados reais que eu coloquei em uma fila separada. oNextChunk() método é a questão aqui. Dependendo do valor deReadAhead Continuarei recebendo o próximo pedaço assim que o último for concluído (Tudo) ou não até que um valor seja necessário, mas não esteja disponível (Nenhum) ou apenas receba o próximo pedaço além dos valores que estão sendo usados no momento (Alguns). Por isso, obter o próximo pedaço deve ser executado em paralelo / não bloquear o valor seguinte. O código do enumerador para isso é:

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
}

Não afirmo que esse código seja perfeito. Desculpe, é tão longo, não tinha certeza de como simplificar. A parte importante é aNextChunk método e a chamada paraTask.Yield(). Essa funcionalidade é usada através de um método de construção estático:

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)
    { ... }
}

questionAnswers(3)

yourAnswerToTheQuestion