Понимание Async / ожидайте правильно. Как это правильно? - PullRequest
0 голосов
/ 24 марта 2020

Представьте себе следующую ситуацию. Существует пользовательский интерфейс, и длительная операция должна вызываться без блокировки основного потока. длительная операция вызывает для себя некоторые другие методы, которые не взаимодействуют с потоком пользовательского интерфейса.

В большинстве случаев методы, вызываемые из метода B и C, имеют синхронные альтернативы своему Asyn c аналоги. Вопрос в том, могут ли они безопасно использоваться вместо их asyn c аналога? Допустим, DbContext.DbSet.Add вместо AddAsyn c

// UI
public async Task UiMethodAsync()
{
    var result = await MethodAAsync();
}

// some component
public async Task<bool> MethodAAsync()
{
    return await MethodBAsync().ConfigureAwait(false);
}

public async Task<bool> MethodBAsync()
{
    return await MethodCAsync().ConfigureAwait(false);
}

public async Task<bool> MethodCAsync()
{
    return await DbContext.Set<TEntit>.AnyAsync().ConfigureAwait(false);
}

Мой вопрос таков: необходимо ли сделать все методы асинхронными, чтобы предотвратить блокировку потока пользовательского интерфейса, или он будет достаточно хорош для создания Методы B и C синхронно выглядят так:

// UI
public async Task UiMethodAsync()
{
    var result = await MethodAAsync();
}

// some component
public Task<bool> MethodAAsync()
{
    return Task.FromResult(MethodB());
}

public bool MethodB()
{
    return MethodC();
}

public bool MethodC()
{
    return DbContext.Set<TEntit>.Any();
}

Конечно, это зависит от того, что делают MethodB и C, взаимодействуют ли они с потоком пользовательского интерфейса или нет. но допустим, что они этого не делают, а просто должны рассчитать вещи и вернуть результат.

Есть ли необходимость сделать их асинхронными? Я думаю, нет. Я думаю, что это, вероятно, позволяет избежать ненужных накладных расходов на управление задачами и потоками.

Ответы [ 3 ]

1 голос
/ 24 марта 2020

Метод, который возвращает Task, создает ожидание, которое не будет блокировать вызывающий поток. По крайней мере, не в течение значительного количества времени. Но это не обеспечивается механизмом asyn c -wait. Можно и даже очень легко написать метод, который нарушает это ожидание. Например:

public Task DoStuffTheWrongWayAsync()
{
    Thread.Sleep(1000); // Simulate a heavy computation, or a blocking call
    return Task.CompletedTask;
}

Любой поток, вызывающий этот метод, будет заблокирован на одну секунду, а затем будет передан завершенное задание. Я не знаю, имеет ли этот antipattern установленное имя. Ложная асинхронность имеет в виду, хотя это также может быть использовано для в случае , когда вызывающая сторона не заблокирована, но вместо этого заблокирован другой плохой поток. Суть в том, что хорошо ведущий себя асинхронный метод должен немедленно возвращать Task, оставляя вызывающий поток свободным для выполнения другой работы (например, для ответа на события пользовательского интерфейса, если это поток пользовательского интерфейса).

1 голос
/ 24 марта 2020

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

Это сложно объяснить, но выделите один бит вашего кода:

public async Task<bool> MethodA()
{
    return Task.FromResult(MethodB());
}

Эквивалентно

public async Task<bool> MethodA()
{
    bool b = MethodB();
    return Task.FromResult(b);
}

Теперь ясно, что первая строка кода блокируется, и Задача не создается до второй строки.

0 голосов
/ 26 марта 2020

Вообще говоря, если вы хотите сделать что-то асинхронное, вы начинаете с самого низкого уровня 1002 * - API, которые фактически выполняют работу ввода-вывода. В вашем примере AnyAsync будет first , сделанным асинхронным. Затем позвольте асинхронности расти оттуда (до MethodC, затем MethodB, затем MethodA и, наконец, UiMethod).

Это нормально, чтобы асинхронность продолжалась "полностью" .

Есть ли необходимость сделать их асинхронными? Я думаю, нет.

Да. Если вы хотите, чтобы они были асинхронными, то они должны быть асинхронными. Task.FromResult для синхронных реализаций; они не будут асинхронными.

Представьте себе следующую ситуацию. Есть пользовательский интерфейс ... методы, которые вызываются из метода B и C, имеют синхронные альтернативы своим аналогам Asyn c. Вопрос в том, могут ли они безопасно использоваться вместо их асин c аналога?

Одна вещь, которую вы можете избежать в мире пользовательского интерфейса - это обернуть вызовы синхронных методов в Task.Run. Например:

public async Task UiMethodAsync()
{
  var result = await Task.Run(MethodA);
}

Это полезный метод для ситуаций, когда у вас есть клиентское приложение, вы не хотите блокировать пользовательский интерфейс, но также не хотите тратить время на преобразование всех код быть асинхронным. Обратите внимание, что не подходит для серверных приложений, таких как ASP. NET.

.
...