我昨天问了这个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();
}
}
}
}
答案 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。
总体上不在后台线程上运行它意味着:
Thread.Sleep(x)
或实际花费的时间会阻止用户界面答案 3 :(得分:1)
tournaments
将在将来某个时候由任务池中的线程之一执行队列task = Task.Run(() => RunLongRunningMethod(cts.Token));
。下一行代码将立即执行。
而
RunLongRunningMethod
将在当前线程上执行代码,直到完成后才会运行下一行代码。