Асинхронный метод без ожидания vs Task.FromResult - PullRequest
0 голосов
/ 03 сентября 2018

Рассмотрим следующий интерфейс:

public interface IProvider
{
    Task<bool> Contains(string key);
}

Это реализация, удовлетворяющая Visual Studio

public Task<bool> Contains(string key)
{
    return Task.FromResult(false);
}

Эта реализация удобна для записи и может показаться достигающей того же:

public async Task<bool> Contains(string key)
{
    return false;
}

Однако Visual Studio бросает шипение и настаивает:

В этом асинхронном методе отсутствуют операторы 'await' и он будет работать синхронно. Подумайте об использовании оператора «await» для ожидания неблокирующих вызовов API или «await TaskEx.Run (...)» для выполнения работы, связанной с ЦП, в фоновом потоке.

Я бы хотел просто проигнорировать это предупреждение и избегать использования Task.FromResult(...).

Есть ли какие-либо негативные последствия при использовании последнего варианта?

1 Ответ

0 голосов
/ 03 сентября 2018

Причина такого "шипения" заключается в том, что компилятору нужно выполнить лот работы, чтобы представить задачу, которая работает всеми ожидаемыми правильными способами, которые вы можете увидеть по компилирование и декомпиляция

Task.FromResult чище, но может иметь дополнительные издержки - в IIRC есть несколько сценариев, в которых Task.FromResult может работать эффективно (возвращая один и тот же объект каждый раз), но я бы не стал полагаться на него.

Существует 2 прагматичных надежных подхода:

  • возвращает повторно использованный статический Task<bool> результат каждый раз
  • используйте ValueTask<bool> - что кажется идеальным здесь, если вы возвращаетесь синхронно большую часть времени

1021 * т.е. *

private readonly static Task<bool> s_False = Task.FromResult(false);
public Task<bool> Contains(string key, string scope)
{
    return s_False ;
}

или

public ValueTask<bool> Contains(string key, string scope)
{
    return new ValueTask<bool>(false);
}

Примечание: второе из них может быть невозможно в этом случае, так как вы не определили интерфейс. Но: если вы когда-либо проектируете интерфейс, который должен разрешать асинхронное использование, но который на самом деле может быть синхронизирован: рассмотрите возможность использования ValueTask<T> в качестве типа обмена, а не Task<T>.

Сгенерированный C # из:

public async System.Threading.Tasks.Task<bool> Contains(string key, string scope)
{
    return false;
}

это что-то вроде:

[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <Contains>d__0 : IAsyncStateMachine
{
    public int <>1__state;

    public AsyncTaskMethodBuilder<bool> <>t__builder;

    private void MoveNext()
    {
        bool result;
        try
        {
            result = false;
        }
        catch (Exception exception)
        {
            <>1__state = -2;
            <>t__builder.SetException(exception);
            return;
        }
        <>1__state = -2;
        <>t__builder.SetResult(result);
    }

    void IAsyncStateMachine.MoveNext()
    {
        //ILSpy generated this explicit interface implementation from .override directive in MoveNext
        this.MoveNext();
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        <>t__builder.SetStateMachine(stateMachine);
    }

    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
        //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
        this.SetStateMachine(stateMachine);
    }
}

[AsyncStateMachine(typeof(<Contains>d__0))]
public Task<bool> Contains(string key, string scope)
{
    <Contains>d__0 stateMachine = default(<Contains>d__0);
    stateMachine.<>t__builder = AsyncTaskMethodBuilder<bool>.Create();
    stateMachine.<>1__state = -1;
    AsyncTaskMethodBuilder<bool> <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}
...