正确了解异步/等待。正确吗?

时间:2020-03-24 11:46:12

标签: c# multithreading asynchronous async-await

想象一下以下情况。有一个UI,并且必须调用长时间运行的操作而不阻塞Main线程。长时间运行的操作会自行调用其他方法,这些方法不会与UI线程交互。

在大多数情况下,从方法B和C调用的方法具有异步替代方法的同步替代方法。问题是:可以安全地使用它们而不是异步使用它们吗?可以说是DbContext.DbSet.Add而不是AddAsync

// 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);
}

我的问题是:是否有必要使所有方法异步以防止UI线程阻塞,还是像这样使方法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在做什么,无论它们是否与ui线程交互。但是,假设他们没有,只需要计算事物并返回结果即可。

是否也需要使它们异步?我觉得不是。我认为这可以避免不必要的管理任务和线程的开销。

3 个答案:

答案 0 :(得分:2)

返回Task的方法将创建不会阻塞调用线程的期望。至少在相当长的时间内没有。但这不是由异步等待机制强制执行的。编写打破这种期望的方法是可能的,而且实际上非常容易。例如:

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

任何调用此方法的线程将被阻塞一秒钟,然后将被交给完成的任务。我不知道此反模式是否具有确定的名称。请记住假异步,尽管这也可以用于the case,即呼叫者未被阻止,但另一个不良线程被阻止。最重要的是,行为良好的异步方法应立即返回Task,使调用线程可以自由执行其他工作(例如,如果是UI线程,则响应UI事件)。

答案 1 :(得分:1)

仅添加async关键字还不足以使您的代码不被阻塞。
除非所有调用一直异步,否则返回Task的函数仍然会阻塞。

很难解释,但是要突出一点代码:

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

等同于

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

现在很明显,第一行代码正在阻塞,并且直到第二行才创建任务。

答案 2 :(得分:1)

通常来说,如果要使某些内容异步,则从最低级别开始-实际在进行I / O工作的API。在您的示例中,AnyAsync将成为异步的 first 事物。然后让异步从那里开始增长(从MethodC开始,然后到MethodB,然后到MethodA,最后到UiMethod)。

"all the way"异步结束是正常的。

是否也需要使它们异步?我想不会。

是的。如果希望它们是异步的,则它们需要是异步的。 Task.FromResult用于同步实现;他们不会是异步的。

想象一下以下情况。有一个UI ...从方法B和C调用的方法具有异步替代品的同步替代品。问题是:可以安全地使用它们而不是异步使用它们吗?

在UI世界中可以避免的一件事是将对同步方法的调用包装在Task.Run中。例如:

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

这是一种有用的技术,适用于您拥有客户端应用程序,不想阻塞UI,又不想花时间将所有代码转换为异步的情况。请注意,这不适用于服务器端应用程序,例如ASP.NET。