我问这个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/