c# - 何时使用 Task.Run 而不是

标签 c# task

我问这个question昨天还是不明白使用的区别

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

task = RunLongRunningMethod(cts.Token);

我已通读 Task.Run Etiquette and Proper Usage并且它似乎主要使用 Task.Run 只要它被正确使用(不在实现中)

有没有关于此的任何其他阅读 Material ,或者有人可以解释两者之间的区别吗?

我的代码在下面,使用这两种方法都可以正常工作。

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();
            }
        }
    }
}

最佳答案

基本上,使用 async/await 贯穿 您的代码库,这样就没有什么阻塞 线程了。 (例如,正如我评论的那样,您当前对 Thread.Sleep 的使用是阻塞的)。

一旦达到这一点,您就已经很热了 Task s 由您的异步函数返回,这些函数将在它们没有取得进一步进展时立即返回。

此时,您需要做出决定。您是否有一项长时间运行的任务受 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 对象属性基本设置。但也不要 Invoke实际执行 CPU 密集型操作之前返回到 UI 线程 - 这就是您首先尝试将它们移动到不同线程的原因!

关于c# - 何时使用 Task.Run 而不是,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51398671/

相关文章:

C# 奇怪的对象行为

c# - C#中严格的字符串到字节编码

linux - 尝试使用 "current"宏时出现编译错误

task - 线程池和上下文切换(任务)?

Ant:如何在应用中回显目标文件的名称

c# - .NET 配置文件 : How to check if ConfigSection is present

c# - .NET 编译器——是否内置了嵌套循环优化?

c# - 如何在 Main 中使用 await

task - CancellationTokenSource 与手动结束任务

c# - MVC3 编译 View 很慢