【问题标题】:How do I get a return value from Task.WaitAll() in a console app?如何从控制台应用程序中的 Task.WaitAll() 获取返回值?
【发布时间】:2014-10-08 18:42:56
【问题描述】:

我正在使用控制台应用程序作为概念验证和获取异步返回值的新需求。

我发现我需要在我的 main 方法中使用 Task.WaitAll() 以避免需要异步“main()”方法,这是非法的。

我现在一直在试图找出一个允许我使用泛型或只返回一个我可以转换的对象的重载,但是在 Main() 中。

【问题讨论】:

    标签: c# .net task-parallel-library console-application async-await


    【解决方案1】:

    您没有从Task.WaitAll 获得返回值。你只用它来等待多个任务完成,然后从任务本身获取返回值。

    var task1 = GetAsync(1);
    var task2 = GetAsync(2);
    Task.WaitAll(task1, task2);
    var result1 = task1.Result;
    var result2 = task2.Result;
    

    如果您只有一个 Task,只需使用 Result 属性。如果任务尚未完成,它将返回您的值并阻塞调用线程:

    var task = GetAsync(3);
    var result = task.Result;
    

    在异步任务(“同步异步”)上同步等待(阻塞)通常不是一个好主意,但我想这对于 POC 来说很好。

    【讨论】:

    • 不确定Task.WaitAll() 的实现是否发生了变化,但这个答案对我不起作用。 Task.WaitAll() 只会阻塞线程并且执行永远不会到达下一行。正如其他答案中所建议的那样,await Task.WhenAll() 似乎是推荐的前进方式。
    • @AdrianHristov 没有任何改变,您的代码中可能只有SynchronizationContext 死锁。正如这个答案所述,阻止异步任务不是一个好主意,但如果您决定这样做,这就是您获得任务结果的方式。
    • 我没有看到类似以下内容的区别:for(var i = 0; i localhost:3000...)); } await Task. WhenAll(taskList); foreach(var task in taskList) { System.Diagnostics.Debug.WriteLine(task.Result); } 我在获取 task.Result 时出错,因为 Task 上没有 Result 属性或方法?
    • @KevinBurton 对于任务的返回类型,您可以使用 Wait() [或重载之一]。对于 Task 的返回类型,其中 T 是返回类型,您可以使用 Result。
    【解决方案2】:

    为获得最佳实践,请使用新的异步处理方式。 而不是

    • Task.WaitAll 使用 await Task.WhenAll
    • Task.WaitAny 使用 await Task.WhenAny

    上面的代码可以写成:

    var task1 = GetAsync(1);
    var task2 = GetAsync(2);
    var results = await Task.WhenAll(task1, task2);
    var result1 = results[0];
    var result2 = results[1];
    

    【讨论】:

    • 这会强制此代码所在的方法具有 async 关键字和 Task 的返回类型。 Task.WaitAll 将允许您返回 void。我不是说这是否更好。
    • 你应该避免返回Task void。
    • Task.WaitAll 要求所有任务具有相同的返回类型
    • 作为本次讨论的更新,请参考:stackoverflow.com/questions/17197699/…
    • 你不应该说“代替”,因为有时我们想要阻塞异步而不是非阻塞。 await 刚刚使您的线程非阻塞。
    【解决方案3】:

    根据上下文和您的需要,您可以使用不同的方法,但我尝试了 i3arnon 和 alltej 的答案,在调试中我发现它们都有相同的问题...我在其中运行了两个代码示例我制作的控制台应用程序。我在代码中设置了一个断点,发现在使用Task.WaitAllTask.WhenAll 的执行行上。当我检查任务时,我发现它们已经运行了 - 任务属性 Status 是 = ran to completion。事实上,我能够完全删除Task.这行代码并仍然分配变量值,所以它没有用。

    我发现真正让这种操作按要求运行并获得所需的返回值是一项非常困难的任务。这是我想出的:

    Task[] taskArray = {
        Task.Factory.StartNew(() => ExecuteConcurrentTasks(1).Result),
        Task.Factory.StartNew(() => ExecuteConcurrentTasks(2).Result)
    };
    Task.WaitAll(taskArray);
    var s = ((Task<string>)taskArray[0]).Result;
    

    这是一个简单的示例,其中创建了一个任务数组,然后使用Task.WaitAll 同时执行两个任务,然后在最后一行中,我将展示如何访问第一个任务的返回值。

    这是包含更多上下文和更多数据的代码:

    private static async void MyAsyncMethod()
    {
        Task[] taskArray = {
            Task.Factory.StartNew(() => ExecuteConcurrentTasks(1).Result),
            Task.Factory.StartNew(() => ExecuteConcurrentTasks(2).Result),
            Task.Factory.StartNew(() => ExecuteConcurrentTasks(3).Result),
            Task.Factory.StartNew(() => ExecuteConcurrentTasks(4).Result)
        };
        Task.WaitAll(taskArray);
        string s1 = ((Task<string>)taskArray[0]).Result;
        string s2 = ((Task<string>)taskArray[1]).Result;
        string s3 = ((Task<string>)taskArray[2]).Result;
        string s4 = ((Task<string>)taskArray[3]).Result;
    }
    
    
    
    private static async Task<string> ExecuteConcurrentTasks(int passedInt)
    {
        string s = "Result: " + passedInt.ToString();
    
        if (passedInt == 4)
        {
            Console.WriteLine(s);
        }
        else if (passedInt == 3)
        {
            Console.WriteLine(s);
        }
        else if (passedInt == 2)
        {
            Console.WriteLine(s);
        }
        else if (passedInt == 1)
        {
            Console.WriteLine(s);
        }
    
        return s;
    }
    

    这是输出:


    更新:

    通过与其他一些开发人员的合作,我对这些方法有了更多的了解。我现在的理解是.WaitAll 将阻止程序其他部分的执行,如果您不只运行一个非常简单的程序,这是一个问题。另外.WhenAll 并不意味着任务不会在该行之前执行,并且都在该行开始异步执行,它只是意味着该块中的代码不会继续超过该点,除非所有指定的任务都已完成正在运行。

    【讨论】:

    • 我对此表示赞同,因为它表明您必须在数组中强制转换任务才能获得结果值。我不明白你为什么这样做,因为数组是那种类型,但你这样做。
    • 我投反对票的原因是Task.Factory.StartNew(() =&gt; ExecuteConcurrentTasks(1).Result) 无缘无故阻塞了ThreadPool 线程,并使用primitive Task.Factory.StartNew 方法而没有明确指定TaskScheduler。正确的是Task.Run(() =&gt; ExecuteConcurrentTasks(1)),或者只是ExecuteConcurrentTasks(1)
    猜你喜欢
    • 2016-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多