【问题标题】:Is there anyway to execute the return statement of a method before await call in that method is completed无论如何在该方法中的等待调用完成之前执行该方法的返回语句
【发布时间】:2020-01-15 00:41:41
【问题描述】:

我在c#代码中有一个异步方法,如演示代码所示--

public async Task<string> Method1()
{
    string s = "some text";
    await method2();
    return s;
}

现在的问题是 - method2 需要几分钟才能完成,但 UI 更新仅取决于 Method1 返回的字符串。 这影响了 UI 性能。有没有办法返回 string 而不等待 await 调用完成。

【问题讨论】:

  • 如果你不想等到method2()完成,那么这个函数设计是错误的。你想用这段代码实现什么。大局观可能有助于更好地解决问题。
  • 如果您不想等待,请不要等待。 await method2(); -> method2();
  • 如果Method2抛出异常会发生什么?
  • 如果返回的字符串不依赖于调用method2(),则将Method1()拆分为两种方法——一种是返回字符串的非异步方法,另一种是等待method2()的异步方法,如果返回的字符串 does 依赖于method2() 那么你别无选择,只能等待它。
  • 我们可以在使用 await 调用 method2() 时使用 ConfigureAwait(false)。作为一种好的做法,ConfigureAwait(false) 有助于提高性能。 msdn.microsoft.com/en-us/magazine/jj991977.aspx

标签: c# asynchronous async-await


【解决方案1】:

你可以写

public string Method1()
{
    Method2();
    return "some text";
}

但是,您会收到警告。

您在Method2 上做火而忘记,表明您不在乎Method2 是否成功、失败、抛出异常甚至在您的应用程序主机关闭之前完成。

我很怀疑这就是你真正想要的?

【讨论】:

  • 另外,如果Method2() 中发生未处理的异常,它可能会导致整个应用程序崩溃。
  • @GabrielLuci 不,它不能,除非它是一个 StackOverflowException 无论如何都会使应用程序崩溃。因为Method1 没有返回Task 对象,所以每个异常都将被丢弃。 IAsyncStateMachine 将简单地将异常存储在默认的 TaskAwaiter 中,稍后将被丢弃。
  • 我刚刚测试了这个。你是对的,如果Method2() 返回一个Task。如果是async void,那么它将使应用程序崩溃。由于原始代码在等待Method2(),所以它会返回一个Task,它是安全的。
【解决方案2】:

您可以使用 Task.Run() 调用异步方法而无需等待回复。

 static void Main(string[] args)
        {
            var t = Task.Run(() => MyMethod());
        }

        static public async void MyMethod()
        {
            //some long running code
        }

此页面更详细:

Simplest way to do a fire and forget method in c# 4.0

【讨论】:

  • 如果该方法已经执行异步方法,则调用Task.Run 毫无意义...您正试图将基于 IO 的执行包装在一个旨在用于基于 CPU 的执行的方法中。
  • 很公平——我认为不等待就直接调用它会产生警告,但在 VS2019 中似乎没有。
  • @FCin:这不是真的。这取决于这段代码在什么上下文中运行,不幸的是我们无法看到。您所说的对于 ASP.NET Core 之类的东西是正确的,但是在控制台应用程序、WPF、WinForms 等中,同步上下文将导致异步任务默认在 UI 线程上运行。使用Task.Run 确保它转到不同的线程。
  • @ChrisPratt 如果例如WPF 会在 UI 线程上安排一个方法调用,然后它会阻塞 UI 线程,阻塞 Windows 消息泵,这不是通常的情况。调度器可能在 UI 线程上调度一个任务,但它不是必须的。根据method2 的实现,它可能会在 ThreadPool 线程上调度它,创建一个计时器/旋转等待,创建一个 windows 管道,返回已完成的任务,创建一个全新的线程,谁知道方法内部有什么。一切都取决于 method2 是否在内部异步等待创建 AsyncTaskMethodBuilder
【解决方案3】:

显然,您在 Method1 的规范中写道,此函数将执行一些需要您调用 Method2 的操作,否则您可以编写程序而不调用 Method2。

这意味着,您的调用者需要一些后置条件。唉,你没有写你的帖子条件,所以我必须用相当抽象的术语来写下面的内容。

假设 Method2 有一个后置条件 Post2(我将这里的前置条件排除在讨论之外)

现在你写了什么作为方法 1 的规范?我知道您编写了实际 Method1 的简化版本,但它似乎类似于:

Method2 将返回一个Task&lt;string&gt;,当等待它时,会将进程的状态更改为:Post2 并且返回值将是一个包含“一些文本”的字符串。

如果这是您的规范,那么您绝对应该等待 Method2。

另一种规范可以是:

Method2 将返回一个Task&lt;string&gt;,当等待时,将启动 Method2,并且返回值将是一个包含“一些文本”的字符串。

如果是这种情况,您不必等待 Method2。您的调用者无法查明 Post2 何时处于活动状态,也无法查明在尝试将机器状态更改为 Post2 时是否出现问题。

可能在某个时候,您的调用者之一想要确定 Method2 已完成,因此 Post2 得到满足。为什么不让调用者决定他是否要等待 Method2?

您可以返回一个包含 Method2 的任务和返回字符串的元组,而不是返回 Task&lt;string&gt;。见Tuples in C# 7

public (string Text, Task Task> Method1()
{
    string s = "some text"; 
    Task method2Task = method2();

    return (Text: s, Task: method2Task);
}

因为您不等待,所以该方法不再是异步的。

当然你可以在一个语句中做到这一点:

return (Text: "Some text", Task: method2() );

你的来电者应该是这样的:

var method1Result = method1();

// I'm not interested in Method2 right now, but I like to process the string result
ProcessText(method1Result.Text);

// if, after a while I want to be certain the Post2 is met, I'll have to await:
await method1Result.Task

【讨论】:

    【解决方案4】:

    您可以通过从 method2 调用中删除 await 并在要返回的字符串对象上使用 Task.FromResult 来做到这一点,但这会效率不高:

    public async Task<string> Method1()
    {
        string s = "some text";
        method2();
        return await Task.FromResult(s);
    }
    

    【讨论】:

    • 这只会同步运行,但await 的开销必须决定同步运行。请参阅 Jodrell 的回答。
    • @GabrielLuci 当然这是正确的方法,但是如果我们不想更改方法签名怎么办?在这种情况下,这将是方式
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-07
    • 2012-07-07
    • 1970-01-01
    • 2017-06-07
    相关资源
    最近更新 更多