Task.Run()

时间:2016-01-12 23:35:54

标签: c# .net asynchronous

Task.Run(()=>{})将操作委托放入队列并返回任务。 在Task.Run()中使用async / await有什么好处吗? 我知道Task.Run()是必需的,因为如果我们想直接使用await,那么调用方法将需要成为Async并且会影响调用的地方。

以下是在Task.Run()中具有异步等待的示例代码。完整示例如下:https://msdn.microsoft.com/en-us/library/hh228607(v=vs.110).aspx

Task.Run(async () => { await new WebClient().DownloadStringTaskAsync("");});

或者可以这样做,

Task.Run(() => new WebClient().DownloadStringTaskAsync("").Result;);

由于Task.Run()和Await都将对工作进行排队并将被线程池选中,因此Task.Run()中的async / await是否有点多余?

5 个答案:

答案 0 :(得分:6)

  

在Task.Run()中使用async / await有什么好处吗?

是。 Task.Run在线程池线程上运行一些操作。如果此类操作执行某些IO工作并异步等待通过await完成IO操作,则在IO操作仍在运行时,系统可以使用此线程池线程进行其他工作。

示例:

Task.Run( async () =>
{
    DoSomeCPUIntensiveWork();

    // While asynchronously waiting for this to complete, 
    // the thread is given back to the thread-pool
    var io_result = await DoSomeIOOperation(); 

    DoSomeOtherCPUIntensiveWork(io_result);
});

答案 1 :(得分:3)

  

在Task.Run()

中使用async / await是否有任何好处

您也可以提出相反的问题:为什么要在Task.Run中包装async / await代码?!

一旦第一个await被命中(对未完成的任务进行操作),异步方法就会返回给调用者。因此,如果异步方法的第一次执行“条带”花费很长时间Task.Run将改变行为:它将导致该方法立即返回并执行线程池上的第一个“条纹”。

这在UI场景中非常有用,因为这样您可以100%确定您没有阻止UI。示例:即使您使用其中一种异步方法,HttpWebRequest也会同步进行DNS解析(这基本上是库错误/设计错误)。这可以暂停UI线程。因此,您可以使用Task.Run 100%确保UI永远不会被阻止超过几微秒。

回到最初的问题:为什么要在Task.Run体内等待?出于同样的原因,您通常会等待:取消阻止该线程。

答案 2 :(得分:1)

在您链接的示例中,主线程被阻塞,直到异步操作完成。通过调用Wait()来阻止它(顺便说一句,这通常是一个坏主意)。

让我们看一下linked示例中DownloadStringAsync的回报:

return Task.Run(async () =>
{
    content = await new WebClient().DownloadStringTaskAsync(address);
    cachedDownloads.TryAdd(address, content);
    return content;
});

为什么要将它包裹在Task中?考虑一下您的选择。如果您不想将其包装在Task中,您将如何确保该方法返回Task<string>并仍然有效?当然,您将方法标记为async!但是,如果您将方法标记为async并且在其上调用Wait,那么您很可能最终会遇到死锁,因为主线程正在等待工作完成,并且你阻止主线程,所以它不能让你知道它已经完成。

当将方法标记为async时,状态机将在调用线程上运行,但在您的示例中,状态机在一个单独的线程上运行,这意味着在主体上几乎没有工作线程。

答案 3 :(得分:-1)

从非异步方法调用异步

当我们尝试在非异步方法中调用异步方法时,我们做了类似的事情。特别是如果异步方法是已知数量。我们使用了更多的TaskFactory但是......适合模式,使其更容易调试,确保每个人都采用相同的方法(并且 - 如果异步 - &gt;同步开始执行错误,则让我们窒息)。< / p>

所以,你的例子

想象一下,在您的示例中,您具有非异步功能。并且,在该功能中,您需要调用等待webClient.DoSomethingAsync()。你不能在一个不是异步的函数中调用等待 - 编译器不会让你。

选项1:Zombie Infestation

你的第一个选择是一直爬行你的调用堆栈,标记每个da * n方法as async并在任何地方添加等待。到处。就像,无处不在。然后 - 因为所有这些方法现在都是异步的,所以你需要使引用它们的方法都是异步的。

顺便说一句,这可能是很多SO爱好者所倡导的方法。因为,&#34;阻止线程是一个坏主意。&#34;

因此。是啊。这个&#34;让异步为异步&#34;方法意味着您的小型库例程只需触及json对象并触及80%的代码。谁打算给CTO打电话让他知道?

选项2:与一个僵尸一起去

或者,你可以将async封装在像你这样的函数中......

return Task.Run(async () => {
     content = await new WebClient().DoSomethingAsync();
     cachedDownloads.TryAdd(address, content);
     return content;
});

Presto ... zombie infestation 已包含在单个代码段中。我将它留给位机制来争论如何/为什么在CPU级别执行。我真的不在乎。我担心没有人必须向CTO解释为什么整个库现在应该是100%异步(或类似的东西)。

答案 4 :(得分:-3)

已确认,用 await 包装 Task.Run 使用 2 个线程而不是一个。

Task.Run(async () => { //thread #1
 await new WebClient().DownloadStringTaskAsync(""); //thread #2
});

假设你有四个这样包装的调用,它将使用 4 x 2 = 8 个线程。

最好用简单的 await 来调用它们。例如:

Task<byte[]> t1 = new WebClient().DownloadStringTaskAsync("");
Task<byte[]> t2 = new WebClient().DownloadStringTaskAsync("");
byte[] t1Result = await t1;
byte[] t2Result = await t2;

这是包装 Task.Run 使用额外线程的证明。 (不使用WebClient来证明这一点)

private static async Task wrapped()
{
    List<Task> tasks = new List<Task>();
    tasks.AddRange(new []
    {
        Task.Run(async() => await new MyThread().RunMe()),
        Task.Run(async() => await new MyThread().RunMe()),
        Task.Run(async() => await new MyThread().RunMe()),
        Task.Run(async() => await new MyThread().RunMe()),
    });

    Thread.Sleep(1000);
    int number = Process.GetCurrentProcess().Threads.Count;
    Console.WriteLine($"While running thread count: {number}");

    await Task.WhenAll(tasks);
}

enter image description here

未包装

private static async Task unwrapped()
{
    List<Task> tasks = new List<Task>();
    Task<int> t1 = new MyThread().RunMe();
    Task<int> t2 = new MyThread().RunMe();
    Task<int> t3 = new MyThread().RunMe();
    Task<int> t4 = new MyThread().RunMe();
    tasks.AddRange(new[] {t1, t2, t3, t4});

    Thread.Sleep(1000);
    int number = Process.GetCurrentProcess().Threads.Count;
    Console.WriteLine($"While running thread count: {number}");

    int i1 = await t1;
    int i2 = await t2;
    int i3 = await t3;
    int i4 = await t4;
}

enter image description here

这里有完整的 POC 代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncThreadDemo
{
    class Program
    {
        static async Task Main(string[] args)
        {
            int number = Process.GetCurrentProcess().Threads.Count;
            Console.WriteLine($"Init thread count: {number}");


            //await wrapped();
            await unwrapped();

            number = Process.GetCurrentProcess().Threads.Count;
            Console.WriteLine($"Done thread count: {number}");

            Console.ReadLine();
        }

        private static async Task wrapped()
        {
            List<Task> tasks = new List<Task>();
            tasks.AddRange(new []
            {
                Task.Run(async() => await new MyThread().RunMe()),
                Task.Run(async() => await new MyThread().RunMe()),
                Task.Run(async() => await new MyThread().RunMe()),
                Task.Run(async() => await new MyThread().RunMe()),
            });

            Thread.Sleep(1000);
            int number = Process.GetCurrentProcess().Threads.Count;
            Console.WriteLine($"While running thread count: {number}");

            await Task.WhenAll(tasks);
        }

        private static async Task unwrapped()
        {
            List<Task> tasks = new List<Task>();
            Task<int> t1 = new MyThread().RunMe();
            Task<int> t2 = new MyThread().RunMe();
            Task<int> t3 = new MyThread().RunMe();
            Task<int> t4 = new MyThread().RunMe();
            tasks.AddRange(new[] {t1, t2, t3, t4});

            Thread.Sleep(1000);
            int number = Process.GetCurrentProcess().Threads.Count;
            Console.WriteLine($"While running thread count: {number}");

            int i1 = await t1;
            int i2 = await t2;
            int i3 = await t3;
            int i4 = await t4;
        }
    }

    public class MyThread
    {
        public static int _counter;

        public async Task<int> RunMe()
        {
            await Task.Run(() =>
            {
                for (int i = 0; i < 2; ++i)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"T{Thread.CurrentThread.ManagedThreadId} {i}");
                }
                Console.WriteLine($"T{Thread.CurrentThread.ManagedThreadId} done");
            });
            return _counter++;
        }
    }
}