并行运行两个异步任务,并在.NET 4.5中收集结果


116

我已经尝试了一段时间,以获取一些我认为使用.NET 4.5会很简单的东西

我想同时启动两个长时间运行的任务,并
以最佳的C#4.5(RTM)方式收集结果

以下作品有效,但我不喜欢,因为:

  • 我想Sleep成为一个异步方法,以便可以使用await其他方法
  • 看起来笨拙 Task.Run()
  • 我认为这根本没有使用任何新的语言功能!

工作代码:

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Task.Run(() => Sleep(5000));    
    var task2 = Task.Run(() => Sleep(3000));

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for a total of " + totalSlept + " ms");
}

private static int Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    Console.WriteLine("Sleeping for " + ms + " FINISHED");
    return ms;
}

非工作代码:

更新:这实际上有效并且是正确的方法,唯一的问题是 Thread.Sleep

该代码不起作用,因为对Sleep(5000)立即执行任务的调用会立即开始运行任务,因此Sleep(1000)直到完成后才运行。即使Sleep是这样async,我也没用await或打电话.Result太早,这是对的。

我以为也许有一种方法可以Task<T>通过调用一个async方法来使非运行状态,这样我就可以调用Start()这两个任务,但是我不知道该如何获取Task<T>从调用异步方法中。

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Sleep(5000);    // blocks
    var task2 = Sleep(1000);

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
}

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    return ms;
}

注意:
设为

3
阻塞发生的时间task1.Result不是at,var task1 = Sleep(5000)因为没有await关键字的Sleep方法是同步的。
阿维斯(Arvis)2013年

Answers:


86

您应该使用Task.Delay而不是Sleep进行异步编程,然后使用Task.WhenAll组合任务结果。这些任务将并行运行。

public class Program
    {
        static void Main(string[] args)
        {
            Go();
        }
        public static void Go()
        {
            GoAsync();
            Console.ReadLine();
        }
        public static async void GoAsync()
        {

            Console.WriteLine("Starting");

            var task1 = Sleep(5000);
            var task2 = Sleep(3000);

            int[] result = await Task.WhenAll(task1, task2);

            Console.WriteLine("Slept for a total of " + result.Sum() + " ms");

        }

        private async static Task<int> Sleep(int ms)
        {
            Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
            await Task.Delay(ms);
            Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
            return ms;
        }
    }

11
这是一个很好的答案...但是我认为这个错误是答案,直到我运行它。然后我明白了。它确实会在5秒内执行。诀窍是不要立即等待任务,而要等待Task.WhenAll。
Tim Lovell-Smith

113
async Task<int> LongTask1() { 
  ...
  return 0; 
}

async Task<int> LongTask2() { 
  ...
  return 1; 
}

...
{
   Task<int> t1 = LongTask1();
   Task<int> t2 = LongTask2();
   await Task.WhenAll(t1,t2);
   //now we have t1.Result and t2.Result
}

2
我+1是因为您将t1,t2声明为Task,这是正确的方法。
Minime

12
我相信此解决方案要求Go方法也要异步,这意味着它公开了异步的能力。如果您想要更类似于Askers的情况,在这种情况下,调用方的Go方法是同步的,但是要异步完成两个独立的任务(即,两个都不需要先完成,而两个都必须在执行继续之前完成),那Task.WaitAll会更好,而您不不需要await关键字,因此不需要调用Go方法本身就是异步的。 两种方法都不是更好,这只是您的目标是什么。
AaronLS

1
无效方法:async void LongTask1() {...}没有Task.Result属性。在以下情况下使用不带T的任务:async Task LongTask1()
阿维斯(Arvis)2013年

我从任何一项任务中都没有得到结果。所以我把它改成了Task<TResult> t1 = LongTask1();现在t1.Result<TResult>是结果的返回类型。您需要return <TResult>在方法中使用才能正常工作。
gilu

1
这可能是值得一提的是,如果你正在做一些非常简单的事情,不想额外t1t2变量,就可以使用new Task(...)。例如:int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));。这种方法的一个陷阱是,编译器将不会识别该变量已分配给它,并且如果您不给它一个初始值,它将被视为未分配。
罗伯特·丹尼斯

3

虽然您的Sleep方法是异步的,但Thread.Sleep不是。异步的整个思想是重用单个线程,而不是启动多个线程。因为您已使用对Thread.Sleep的同步调用进行了阻止,所以它将无法正常工作。

我假设这Thread.Sleep是您实际想要做的简化。可以将您的实际实现编码为异步方法吗?

如果您确实需要运行多个同步阻塞调用,请看其他地方!


谢谢理查德-是的,当我实际使用我的服务电话时,它似乎可以正常工作
Simon_Weaver

那么如何运行异步?我的应用程序,文件做大量的文件交换和等待,大约5秒钟,然后另一个进程,当我“时,所有”它首先先运行,然后第二,即使我说:var x = y(),而不是var x=await y()y().wait()仍然是一直等,如果异步不能自己处理,我该怎么办?注意y用异步装饰,我希望它能在所有时间内完成所有工作,而不是在分配它的地方进行编辑:我只是在什么时候对我的伴侣说,让我们尝试一下Task.Factory,他说它在我爆发时起作用此类的一面
deadManN

2

要回答这一点:

我希望睡眠是一个异步方法,以便它可以等待其他方法

您可以这样重写Sleep函数:

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    var task = Task.Run(() => Thread.Sleep(ms));
    await task;
    Console.WriteLine("Sleeping for " + ms + "END");
    return ms;
}

static void Main(string[] args)
{
    Console.WriteLine("Starting");

    var task1 = Sleep(2000);
    var task2 = Sleep(1000);

    int totalSlept = task1.Result +task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
    Console.ReadKey();
}

运行此代码将输出:

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms

2

现在是周末

    public async void Go()
    {
        Console.WriteLine("Start fosterage...");

        var t1 = Sleep(5000, "Kevin");
        var t2 = Sleep(3000, "Jerry");
        var result = await Task.WhenAll(t1, t2);

        Console.WriteLine($"My precious spare time last for only {result.Max()}ms");
        Console.WriteLine("Press any key and take same beer...");
        Console.ReadKey();
    }

    private static async Task<int> Sleep(int ms, string name)
    {
            Console.WriteLine($"{name} going to sleep for {ms}ms :)");
            await Task.Delay(ms);
            Console.WriteLine("${name} waked up after {ms}ms :(";
            return ms;
    }

0

本文有助于解释很多事情。它是常见问题解答样式。

异步/等待常见问题解答

这部分解释了为什么Thread.Sleep在同一原始线程上运行-导致我最初的困惑。

关键字“ async”是否会导致方法的调用排队到ThreadPool?要创建一个新线程?要发射火箭飞船?

不,不。请参阅前面的问题。“ async”关键字向编译器指示可以在方法内部使用“ await”,以便该方法可以在等待点暂停并在等待的实例完成时异步地恢复执行。这就是为什么如果标记为“异步”的方法内部没有“唤醒”的情况,则编译器会发出警告。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.