我正在开展一个多任务网络项目,我是Threading.Tasks
的新人。我实现了一个简单的Task.Factory.StartNew()
,我想知道如何使用Task.Run()
?
以下是基本代码:
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
我在对象浏览器中查看了System.Threading.Tasks.Task
,但找不到类似Action<T>
的参数。只有Action
需要void
参数且没有类型。
只有两件类似的东西:static Task Run(Action action)
和static Task Run(Func<Task> function)
但不能同时发布参数。
是的,我知道我可以为它创建一个简单的扩展方法,但我的主要问题是我们可以用Task.Run()
在单行上写吗?
答案 0 :(得分:76)
private void RunAsync()
{
string param = "Hi";
Task.Run(() => MethodWithParameter(param));
}
private void MethodWithParameter(string param)
{
//Do stuff
}
修改强>
由于受欢迎的需求,我必须注意启动的Task
将与调用线程并行运行。假设默认TaskScheduler
,这将使用.NET ThreadPool
。无论如何,这意味着您需要考虑传递给Task
的任何参数,因为这些参数可能被多个线程同时访问,从而使它们成为共享状态。这包括在调用线程上访问它们。
在上面的代码中,案例完全没有实际意义。字符串是不可变的。这就是我用它们作为例子的原因。但是说你没有使用String
......
一种解决方案是使用async
和await
。默认情况下,这将捕获调用线程的SynchronizationContext
,并在调用await
之后为方法的其余部分创建一个延续,并将其附加到创建的Task
。如果此方法在WinForms GUI线程上运行,则它将是WindowsFormsSynchronizationContext
类型。
继续将在回发到捕获的SynchronizationContext
后运行 - 仅在默认情况下再次运行。因此,您将在await
电话后回到您开始使用的主题。您可以通过各种方式更改此设置,尤其是使用ConfigureAwait
。简而言之,该方法的其余部分将不会继续,直到之后 Task
已在另一个线程上完成。但调用线程将继续并行运行,而不是方法的其余部分。
这等待完成运行方法的其余部分可能是也可能不是。如果该方法中的任何内容以后都没有访问传递给Task
的参数,您可能根本不想使用await
。
或许您可以在稍后的方法中使用这些参数。没有理由立即await
因为你可以继续安全地工作。请记住,您可以将变量中返回的Task
和await
存储在其中 - 即使是使用相同的方法。例如,一旦你做了一些其他的工作后,你需要安全地访问传递的参数。同样,当您运行它时,不需要在await
权限上Task
。
无论如何,关于传递给Task.Run
的参数使这个线程安全的简单方法是这样做:
您必须先使用RunAsync
装饰async
:
private async void RunAsync()
重要提示
优选地,标记为async
的方法不应返回void,如链接文档所述。常见的例外是事件处理程序,例如按钮点击等。他们必须归还无效。否则,我总是在使用Task
时尝试返回Task<TResult>
或async
。出于好几个原因,这是一种很好的做法。
现在您可以await
运行Task
,如下所示。如果没有await
,则无法使用async
。
await Task.Run(() => MethodWithParameter(param));
//Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another
因此,一般情况下,如果您await
任务,您可以避免将传入的参数视为潜在的共享资源,同时具有从多个线程修改某些内容的所有缺陷。另外,请注意closures。我不会深入介绍这些内容,但链接的文章做得很好。
旁注
有点偏离主题,但要小心使用WinForms GUI线程上的任何类型的“阻塞”,因为它标有[STAThread]
。使用await
根本不会阻止,但有时我会看到它与某种阻塞一起使用。
“阻止”在引号中,因为您在技术上cannot block the WinForms GUI thread。是的,如果你在WinForms GUI线程上使用lock
,它将仍然会消息,尽管你认为它被“阻止”了。不是。
在极少数情况下,这可能会导致奇怪的问题。例如,绘画时你永远不想使用lock
的原因之一。但这是一个边缘而复杂的案例;但是我看到它会导致疯狂的问题。所以我完整地注意到了它。
答案 1 :(得分:25)
使用变量捕获来“传入”参数。
var x = rawData;
Task.Run(() =>
{
// Do something with 'x'
});
你也可以直接使用rawData
但是你必须小心,如果你在任务之外更改rawData
的值(例如for
循环中的迭代器)它将会也改变任务内部的值。
答案 2 :(得分:5)
我知道这是一个旧帖子,但我想分享一个我最终必须使用的解决方案,因为接受的帖子仍有问题。
问题:
正如Alexandre Severino指出的那样,如果param
(在下面的函数中)在函数调用后不久发生了变化,那么MethodWithParameter
中可能会出现一些意外行为。
Task.Run(() => MethodWithParameter(param));
我的解决方案:
为了解释这一点,我最后写了一些更像下面的代码:
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
这使我能够安全地异步使用参数,尽管在启动任务后参数变化非常快(这导致了发布的解决方案的问题)。
使用此方法,param
(值类型)传入其值,因此即使异步方法在param
更改后运行,p
也将具有任何值{{1}当这行代码运行时。
答案 3 :(得分:3)
只需使用Task.Run
即可var task = Task.Run(() =>
{
//this will already share scope with rawData, no need to use a placeholder
});
或者,如果您想在方法中使用它并等待稍后的任务
public Task<T> SomethingAsync<T>()
{
var task = Task.Run(() =>
{
//presumably do something which takes a few ms here
//this will share scope with any passed parameters in the method
return default(T);
});
return task;
}
答案 4 :(得分:3)
从现在起你也可以:
Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)
答案 5 :(得分:1)
目前尚不清楚最初的问题是否与我遇到的问题相同:想要在循环内最大化CPU线程的计算量,同时保留迭代器的值并保持内联以避免将大量变量传递给工作函数。
for (int i = 0; i < 300; i++)
{
Task.Run(() => {
var x = ComputeStuff(datavector, i); // value of i was incorrect
var y = ComputeMoreStuff(x);
// ...
});
}
我通过更改外部迭代器并使用gate对其值进行本地化来使其工作。
for (int ii = 0; ii < 300; ii++)
{
System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1);
Task.Run(() => {
int i = ii;
handoff.Signal();
var x = ComputeStuff(datavector, i);
var y = ComputeMoreStuff(x);
// ...
});
handoff.Wait();
}
答案 6 :(得分:0)
想法是避免使用上述信号。 将int值泵入结构可防止这些值(在结构中)更改。 我有以下问题:循环变量i在调用DoSomething(i)之前会发生变化(在调用()=> DoSomething(i,i i)之前,我在循环末尾递增)。有了结构,它不再发生了。令人讨厌的错误:DoSomething(i,i i)看起来很棒,但是永远不确定是否每次都以不同的i值调用它(或者在i = 100时仅被调用100次),因此-> struct
struct Job { public int P1; public int P2; }
…
for (int i = 0; i < 100; i++) {
var job = new Job { P1 = i, P2 = i * i}; // structs immutable...
Task.Run(() => DoSomething(job));
}