使用Task.FromResult(0)的异步等待Async Await

4

不确定自己是否混淆了对async await的理解,但是这里有一个我卡住的问题。考虑以下人为的例子:

  1. This code blocks UI

    public async void LoginButtonClicked()
    {
     //create a continuation point so every following statement will get executed as ContinueWith
     await Task.FromResult(0);
     //this call takes time to execute
     Remote.Login("user","password");
    }
    
  2. But this does not (obviously)

     public void LoginButtonClicked()
     {
     Task.Run(()=>{ Remote.Login("user","password");});
      }
    

我喜欢使用方法1,因为我不想使用Task.Run来旋转长时间的工作,而是更喜欢框架来处理。但问题是调用方法1似乎会阻塞。


1
只有当 await 右侧的任务尚未完成时,它才会执行任何特殊操作。而 Task.FromResult 总是返回一个已完成的任务,因此该方法会在 await 点之后继续顺利执行。 - Damien_The_Unbeliever
1
这篇旧的Eric Lippert博客文章可能会有所帮助:“异步方法的整个重点在于尽可能地保持在当前线程上。” - Damien_The_Unbeliever
不应该使用await调用Task.FromResult方法:我应该在Task.FromResult方法调用上使用await吗? - S.Serpooshan
3个回答

5

使用await/async只有在所有长时间运行的操作都是异步的情况下,才能防止阻塞UI。在您的示例中,Remote.Login是同步调用,因此无论前面的await代码行执行什么操作,它都会阻塞您的UI。

您需要获取实际长时间运行操作的异步版本(例如返回Task的内容),或者如果不可能,则可以使用Task.Run将该工作移至ThreadPool

如果可能,您所需的:

public async void LoginButtonClicked()
{
    await Remote.LoginAsync("user","password");
    // do anything else required
}

2

每个异步方法都有其上下文。

当任务开始时,它可能会在一个新的SynchronizationContext中运行。“可能”是因为如果任务已经完成,例如Task.FromResult(0),则不会创建其他SynchronizationContext,而是使用原始的上下文。

等待任务意味着当任务完成时,下一条语句将在原始的SynchronizationContext中运行。

可以使用Task.ConfigureAwait(continueOnCapturedContext:false)更改此行为。这意味着下一条语句将继续在相同的上下文中运行。但是,通过执行Task.FromResult(0)。ConfigureAwait(false)不会改变任何内容,因为任务已经完成,将使用原始上下文。

因此,你的Remote.Login("user","password");将在原始上下文中运行,从而阻塞在同一上下文中运行的UI线程。

如果你有类似以下的内容:

public async void LoginButtonClicked()
{
  await Task.Delay(5000).ConfigureAwait(false);
  Remote.Login("user","password");
}

然后,Remote.Login(“user”,“password”); 将在线程池上下文中执行,因此与原始UI上下文不同。因此,修复您的代码的最佳方法是创建一个Remote.LoginAsync(),如@Nicholas W的答案所述。
性能注意事项:如果您有一个带有多个等待语句的异步方法,并且您不需要其中一些等待在UI或Web应用程序线程上进行工作,则可以使用Task.ConfigureAwait(false)来防止多次切换到UI / web-app上下文,以减少其执行时间。

-2
  1. 你可以并行运行Task.FromResult(0);,但仍需等待Remote.Login("user","password");完成。
  2. 你可以异步地运行Remote.Login("user","password");。

你需要创建Remote.Login的异步版本。

    async Task LoginAsync(string user, string password)
    {
        Remote.Login(user, password);
        await Task.FromResult(0);
    }

并将其命名为

    public async void LoginButtonClicked()
    {
        await LoginAsync("user", "password");
    }

1
这是一个同步方法,不是异步方法。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接