【问题标题】:C# async/await await doesn't awaitC# async/await 等待不等待
【发布时间】:2019-10-21 17:39:26
【问题描述】:

我正在使用 .Net 4.7.2 和 C# 7

在一个类中有多个方法,都是无效的。其中一些可以异步执行,另一些必须按顺序执行。所以我定义了一个平行列表

private void ExecuteSequential()
{
    SomeMethod1();
    SomeMethod2();
    ExecuteParallelAsync();
    SomeMethod3();
    SomeMethod4();
}


private async void ExecuteParallelAsync()
{
    List<Action> methods = new List<Action>()
    {
        MyMethod1,
        MyMethod2,
        MyMethod3
    };


    await Task.Run( () => { Parallel.ForEach( methods , ( currentMethod ) => currentMethod.Invoke() ); } );            
}

SomeMethod3 恰好被执行之前 ExecuteParallelAsync 应该完全完成。 因为不是这样,显然我对 async 和 await 的使用有问题。

我做错了什么?

提前致谢!

【问题讨论】:

    标签: c# async-await


    【解决方案1】:

    async void - 所有赌注都取消了;实际上,您应该从不编写async void 方法,除非您处于tiny tiny 需要它的极端极端情况集合中,例如异步事件处理程序。

    在 SomeMethod3 恰好被执行之前 ExecuteParallelAsync 应该完全完成。

    它怎么可能知道?它是:async void,这意味着它没有给调用者任何指示状态的东西 em>。

    这样做,它必须是:

    private async Task ExecuteParallelAsync() // or ValueTask
    

    必须在呼叫站点等待它

    await ExecuteParallelAsync();
    

    【讨论】:

    • 非常感谢马克。这确实解决了问题。但是,我想补充一点。为了能够使用 await ExecuteParallelAsync();,您还必须将 ExecuteSequentialvoid 设置为 async Task
    • @GeorgeDuke 关键点是添加async,但是是的:最好使用async Task,而不是async void - 出于同样的原因
    【解决方案2】:

    Marc 的答案是正确的解决方案,但我想进一步解释原因。

    了解async 方法的一个非常关键的事情是:

    1. 它们同步启动。
    2. 如果使用了await 关键字,并且await 给出了不完整的Task该方法会返回 - 是的,即使您的方法只完成了一半。神奇之处在于它返回的

    让我们看看你的代码做了什么:

    1. 致电ExecuteParallelAsync()
    2. ExecuteParallelAsync() 开始同步运行。
    3. Task.Run 返回一个尚未完成的 Task 对象。
    4. await 关键字检查Task,发现它不完整,所以它创建一个新的Task,注册该方法的其余部分作为Task 的延续(没有剩余在这种情况下要做,但它仍然会发生)并返回
    5. 因为返回类型是void,它不能返回Task。但它仍然会返回!

    所有这一切意味着ExecuteParallelAsync()Task.Run 完成之前返回,而ExecuteSequential() 无法判断工作何时真正完成。

    这通常被称为“一劳永逸”(即“去做这件事,我不在乎你何时或是否完成”)。

    正如 Marc 的回答所指出的,如果您想知道什么时候完成,您需要返回一个 Task,然后您可以通过 await 知道什么时候完成。

    Microsoft 有关于异步编程的非常好的文章,您可能会从阅读中受益:Asynchronous programming with async and await

    【讨论】:

    • 非常感谢加布里埃尔,正如您所想象的那样,我是这个话题的新手,所以我感谢您的努力和解释。
    【解决方案3】:

    如果你想要一个没有 async/await 糖关键字的实现:

    private void ExecuteParallelAsync()
    {
        List<Action> methods = new List<Action>()
        {
            MyMethod1,
            MyMethod2,
            MyMethod3
        };
    
        Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray())   
    }
    

    【讨论】:

    • 请注意Task.WaitAll 阻塞了当前线程(它不是异步的)。
    • 这是一个很好的评论。 MyMethod1、MyMethod2、MyMethod3 将在 SomeMethod2() 和 SomeMethod3() 之间并行执行
    • 感谢 Tohm,但不幸的是,与我不正确的实现相比,结果是相同的。至少在我的情况下。
    • 您是否在 ExecuteParallelAsync 之前删除了“异步”? MyMethod1、MyMethod2、MyMethod3 是同步函数吗?
    • 是的,我做到了。就像你说的我测试了 void ExecuteParallelAsync (我知道,在这种特殊情况下命名是错误的)。是的,ExecuteParallelAsync 调用的所有方法都不是异步编写的。签名始终是 void Something 没有异步,没有任务。
    【解决方案4】:

    第 1 步,重现

    public void Method()
        {
            SomeMethod();
            SomeMethod();
            ExecuteParallelAsync(); //< ---fire and forget 
            SomeMethod();
            SomeMethod();
            
            Console.ReadLine();
        }
    
        private void SomeMethod() => Console.WriteLine($"SomeMethod");
        
        private async void ExecuteParallelAsync()
        {
            Console.WriteLine($"START");
            await Task.Delay(TimeSpan.FromMilliseconds(100));
            Console.WriteLine($"END");
        }
    

    输出

    SomeMethod
    SomeMethod
    START
    SomeMethod
    SomeMethod
    END
    

    第 2 步,尝试修复

    “哦,是的,我忘了等待我的功能”

    await ExecuteParallelAsync(); //Change 1. "I forgot to await"
    

    输出

    Compilation error (line 12, col 3): The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
    Compilation error (line 12, col 3): Cannot await 'void'
    

    第 3 步,修复

    解决步骤 2 中的两个错误。

    // Change 2
    // was:public void Main()  
    // which caused: "The 'await' operator can only (...)"
    public async Task Main() 
    (...)
    // Change 3
    // was: private async void ExecuteParallelAsync()
    // which caused "Compilation error (line 12, col 3): Cannot await 'void'"
    private async Task ExecuteParallelAsync()
    

    运行。

    输出:

    SomeMethod
    SomeMethod
    START
    END
    SomeMethod
    SomeMethod
    

    作为参考,如果没有 await 关键字,我们本可以等待 ExecuteParallelAsync,但 await 在所有情况下都是正确的方式,除了 tiny 少数人不是这样。

    【讨论】:

    • 感谢 tymtam,但我不能使用静态方法,因为我需要对象,即当前使用的类实例。
    • 静态在这里并不重要。给我一秒钟,我会更新代码。
    • 再次感谢,但 Marc 和 Gabriel 指出永远不应该使用 async void。你不同意吗?
    • 我不反对,我的回答说明了为什么不应该使用它。
    【解决方案5】:

    一个完整的小例子:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace test
    {
        class Program
        {
            static void Main(string[] args)
            {
                SomeMethod1();
                ExecuteParallelAsync();
                SomeMethod2();
                Console.ReadKey();
            }
    
            static void SomeMethod1()
            {
                Console.WriteLine("SomeMethod1");
            }
            static void SomeMethod2()
            {
                Console.WriteLine("SomeMethod2");
            }
    
            static void ExecuteParallelAsync()
            {
                Console.WriteLine("\tstart of ExecuteParallelAsync");
                var rd = new Random();
    
                List<Action> methods = new List<Action>()
                {
                    ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod1"); },
                    ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod2"); },
                    ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod3"); },
                };
                Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray());
    
                Console.WriteLine("\tend of ExecuteParallelAsync");
            }
    
        }
    }
    

    结果示例:

    SomeMethod1
            start of ExecuteParallelAsync
    MyMethod2
    MyMethod3
    MyMethod1
            end of ExecuteParallelAsync
    SomeMethod2
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-11
      • 2016-03-08
      • 2020-08-03
      • 2022-11-07
      • 2021-01-26
      • 1970-01-01
      • 1970-01-01
      • 2019-04-14
      相关资源
      最近更新 更多