什么时候使用Task.Run

时间:2018-07-18 09:40:09

标签: c# task

我昨天问了这个question,但仍然不了解使用

的区别
task = Task.Run(() => RunLongRunningMethod(cts.Token));

task = RunLongRunningMethod(cts.Token);

我已经阅读了Task.Run Etiquette and Proper Usage,并且似乎主要使用Task.Run,​​只要正确使用了它(在实现中未使用)

关于此还有其他阅读材料吗?或者有人可以解释两者之间的区别吗?

下面是我的代码,使用这两种方法都可以。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private static HttpClient client { get; set; }
        private Task task { get; set; }
        private CancellationTokenSource cts { get; set; }
        private bool buttonStartStopState { get; set; }

        public Form1()
        {
            InitializeComponent();
        }

        private async Task RunLongRunningMethod(CancellationToken cancellationToken)
        {
            try
            {
                while (true)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    // Some CPU bound work here

                    // Then call async
                    var success = await GetUrlAsync(@"https://www.bbc.co.uk/");
                    Thread.Sleep(2000); // simulate blocking only
                }
            }
            catch (OperationCanceledException)
            {
                // Just exit without logging. Operation cancelled by user.
            }
            catch (Exception ex)
            {
                // Report Error
            }
        }

        private async Task<bool> GetUrlAsync(string url)
        {
            if (client == null)
            {
                client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true, AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip });
                client.BaseAddress = new Uri("https://www.bbc.co.uk/");
                client.DefaultRequestHeaders.Add("Accept", "*/*");
                client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
                client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/62.0");
                client.DefaultRequestHeaders.Connection.Add("Keep-Alive");
                client.DefaultRequestHeaders.Add("DNT", "1");
            }

            var response = await client.GetAsync(url);
            var contents = await response.Content.ReadAsStringAsync();

            Debug.WriteLine($"{DateTime.Now} {response.StatusCode}");
            return true;
        }

        private void buttonStartStop_Click(object sender, EventArgs e)
        {
            buttonStartStopState = !buttonStartStopState;
            if(buttonStartStopState)
            {
                cts = new CancellationTokenSource();

                // What is difference between this
                //task = Task.Run(() => RunLongRunningMethod(cts.Token));

                // And this?
                task = RunLongRunningMethod(cts.Token);

                // This always runs instantly
                Debug.WriteLine($"{DateTime.Now} buttonStartStopState:{buttonStartStopState}");
            }
            else
            {
                cts.Cancel();
                cts = null;
            }
        }

        private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(cts != null)
            {
                cts.Cancel(); // CancellationTokenSource
                cts = null;
            }

            if (!task.IsCompleted)
            {
                //this.Hide();
                e.Cancel = true;
                await task;
                this.Close();
            }
        }
    }
}

4 个答案:

答案 0 :(得分:3)

基本上,在整个代码库中使用async / await 整个,这样就不会阻塞线程。 (例如,正如我所评论的那样,您当前对Thread.Sleep的使用正在被阻止)。

一旦达到这一点,异步函数就已经返回了热门的Task,并且这些函数没有取得进一步的进展就会立即返回。

这时,您要做出决定。您是否有受 CPU 约束的长期运行的任务?如果是这样,您可能考虑使用Task.Run的时间是 ,因为这明确要求在其他地方(线程池)完成工作。通常,允许I / O主导的任务短暂返回UI线程通常是 ok ,这是默认情况下的操作,这意味着您无需执行任何特殊操作即可访问UI对象。

希望在这一点上,您根本不想在示例中使用Task.Run


但是,长时间运行的任务可能是真正的异步I / O操作和一些CPU密集型操作的组合,并且您仍然不希望这些操作占用UI线程。在这一点上,您通常应该考虑在等待时间上使用ConfigureAwait(false)。但是您可能希望在这里也使用Task.Run

无论哪种情况,如果您想再次与UI对象进行交互,则都必须Invoke回到UI线程。确保在正确的“粒度”上执行此操作。不要Invoke 5或6 UI对象上的属性的基本设置。但是也不要在真正执行CPU密集型操作之前Invoke回到UI线程上-这就是为什么您首先尝试将它们移到另一个线程上的原因!

答案 1 :(得分:2)

RunLongRunningMethod(cts.Token);在同一线程中立即执行操作。这意味着,如果您的代码位于UI线程中,它将阻止您的UI。

task = Task.Run(() => RunLongRunningMethod(cts.Token));相反意味着您要立即执行您的操作。该行将任务排队在ThreadPool上运行,并返回该工作的任务句柄。

通常,我们使用:

Task.Run,当您想执行长时间运行的代码并且不等待任务完成时。后台计算,即和

task = RunLongRunningMethod通常在您要执行await任务时,即

await RunLongRunningMethod(token);
//this method DoSomeThing is not executed until your your long-running code finishs
DoSomeThing();

答案 2 :(得分:2)

只需添加到之前的两个答案

看起来您正在WinForms中运行,该WinForms以STA(单线程公寓)模型运行,这意味着只有线程处理UI排队的消息。

因此,当您运行task = Task.Run(() => RunLongRunningMethod(cts.Token));时,它将在线程池线程上执行所有操作,默认情况下,task = RunLongRunningMethod(cts.Token);会在Thread.Sleep(2000); // simulate blocking only的时间内阻止UI,因为您的等待调用将是您没有ConfigureAwait(false),因此排队到UI Dispatcher。

总体上不在后台线程上运行它意味着:

  1. 您的Thread.Sleep(x)或实际花费的时间会阻止用户界面
  2. 您将对调度程序施加更大的压力,因为每次等待都将由UI调度程序执行。 (在您的实例中,如果只是一次等待,那不是问题,但是,如果您开始进行100甚至1000次等待,这可能会开始显示明显的性能损失!)

答案 3 :(得分:1)

tournaments

将在将来某个时候由任务池中的线程之一执行队列task = Task.Run(() => RunLongRunningMethod(cts.Token)); 。下一行代码将立即执行。

RunLongRunningMethod

将在当前线程上执行代码,直到完成后才会运行下一行代码。