Task.Run是否有参数?


87

我正在从事一个多任务网络项目,并且是的新手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()


目前尚不清楚您希望参数的是什么。它从哪里来?如果您已经掌握了它,只需用lambda表达式捕获它即可
Jon Skeet 2015年

@JonSkeetrawData是具有容器类(如DataPacket)的网络数据包,我正在重用此实例以减轻GC压力。因此,如果我rawData直接在中使用Task它,则可以(可能)在Task处理它之前对其进行更改。现在,我想可以为其创建另一个byte[]实例。我认为这对我来说是最简单的解决方案。
MFatihMAR

是的,如果您需要克隆字节数组,则可以克隆字节数组。拥有Action<byte[]>并不会改变这一点。
乔恩·斯基特

这里是一些将参数传递给任务的好的解决方案
Just Shadow

Answers:


116
private void RunAsync()
{
    string param = "Hi";
    Task.Run(() => MethodWithParameter(param));
}

private void MethodWithParameter(string param)
{
    //Do stuff
}

编辑

由于需求旺盛,我必须注意,Task启动将与调用线程并行运行。假设默认值TaskScheduler将使用.NET ThreadPool。无论如何,这意味着您需要考虑传递给的任何参数,Task因为多个线程可能同时访问它们,从而使它们处于共享状态。这包括在调用线程上访问它们。

在我上面的代码中,这种情况完全没有意义。字符串是不可变的。这就是为什么我以它们为例。但是说你不使用String...

一种解决方案是使用asyncawait。默认情况下,这将捕获SynchronizationContext调用线程的,并在调用后将方法的其余部分创建一个延续await并将其附加到created Task。如果此方法在WinForms GUI线程上运行,则其类型为WindowsFormsSynchronizationContext

延续将在回传到捕获的内容之后运行SynchronizationContext-再次默认情况下。因此,您将回到await调用之后开始的线程上。您可以通过多种方式更改此设置,特别是使用ConfigureAwait。总之,该方法的其余部分将不会继续,直到之后Task已完成了对另一个线程。但是调用线程将继续并行运行,而不是其余方法。

等待完成本方法其余部分的等待可能会,也可能不会。如果该方法中的任何内容以后Task都无法访问传递给的参数,则您可能根本不想使用await

或者,也许您稍后在方法中使用这些参数。没有理由await立即这样做,因为您可以继续安全地进行工作。请记住,您可以将Task返回值存储在变量中,await以后再存储在该变量中,即使使用相同的方法也是如此。例如,完成一堆其他工作后,一旦需要安全地访问传递的参数。同样,你也没有需要awaitTask,当你运行它的权利。

无论如何,使传递的参数相对于线程安全的一种简单方法Task.Run是:

你必须先装饰RunAsyncasync

private async void RunAsync()

重要的提示

如链接文档所述,最好标记的方法不应返回void。常见的例外是事件处理程序,例如单击按钮等。他们必须返回无效。否则,在使用时,我总是尝试返回a或。出于很多原因,这是一个好习惯。async TaskTask<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此任务,则可以避免将传入的参数视为潜在的共享资源,而同时又有一次修改多个线程中的内容的所有陷阱。另外,当心闭包。我不会深入介绍这些内容,但是链接的文章对此做得很好。

边注

主题不多,但是请小心在WinForms GUI线程上使用任何类型的“阻止”,因为它用标记了[STAThread]。使用await完全不会阻止,但我有时确实看到它与某种阻止结合使用。

“ Block”用引号引起来,因为从技术上讲您不能阻止WinForms GUI线程。是的,如果你使用lock上的WinForms GUI线程它仍然抽取消息,尽管你认为它的“封杀”。不是。

在极少数情况下,这可能导致奇怪的问题。lock例如,您永远不想在绘画时使用的原因之一。但这是一个边缘且复杂的案例。但是我已经看到它会引起疯狂的问题。因此,出于完整性考虑,我将其记录下来。


21
您不在等待Task.Run(() => MethodWithParameter(param));。这意味着如果param被修改Task.Run,你可能对意想不到的效果MethodWithParameter
Alexandre Severino

7
为什么错了,为什么这是一个可以接受的答案。它根本不等于传递状态对象。
Egor Pavlikhin

6
@ Zer0状态对象是Task.Factory.StartNew msdn.microsoft.com/zh-cn/library/dd321456(v=vs.110).aspx中的第二个参数,它在出现时保存对象的值调用StartNew,而您的答案将创建一个闭包,该闭包将保留引用(如果在任务运行之前参数值的更改也会在任务中更改),因此您的代码根本不等于问题的含义。答案确实是无法用Task.Run()编写它。
Egor Pavlikhin

2
@ Zer0(用于带有闭包的Task.Run和带有第二个参数的Task.Factory.StartNew)(每个链接与Task.Run不同)的行为有所不同,因为在后一种情况下,将进行复制。我的错误是在原始注释中一般性地引用了对象,我的意思是它们并不完全等效。
Egor Pavlikhin

3
在阅读Toub的文章时,我将突出显示这句话“您将使用接受对象状态的重载,对于性能敏感的代码路径,它可用于避免闭包和相应的分配”。我认为这是@Zero在考虑Task.Run over StartNew用法时所隐含的含义。
davidcarr

35

使用变量捕获来“传递”参数。

var x = rawData;
Task.Run(() =>
{
    // Do something with 'x'
});

您也可以rawData直接使用,但是必须小心,如果您更改rawData任务外部的值(例如,for循环中的迭代器),它也会更改任务内部的值。


11
考虑到+1可能在调用后立即更改的重要事实,所以+1 Task.Run
Alexandre Severino

1
这将如何帮助您?如果在任务线程内使用x,并且x是对对象的引用,并且在任务线程运行时同时修改了该对象,则可能导致严重破坏。
奥维(Ovi)

1
@ Ovi-WanKenobi是的,但这不是这个问题。这是如何传递参数。如果您将对对象的引用作为参数传递给普通函数,那么那里也会有完全相同的问题。
Scott Chamberlain

是的,这不起作用。我的任务在调用线程中没有对x的引用。我只是空。
大卫·普赖斯

7

从现在开始,您还可以:

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)

这是最好的答案,因为它允许传递状态,并避免了Kaden Burgart的答案中提到的可能情况。例如,如果您需要将一个IDisposable对象传递到任务委托中以解决ReSharper警告“捕获的变量已放置在外部作用域中”,则这样做非常好。与普遍的看法相反,使用Task.Factory.StartNew而不是Task.Run传递状态没有什么错。看这里
Neo

7

我知道这是一个旧线程,但是我想分享一个最终不得不使用的解决方案,因为接受的帖子仍然有问题。

问题:

正如Alexandre Severino指出的那样,如果param(在下面的函数中)在函数调用后不久发生更改,您可能会在中出现一些意外行为MethodWithParameter

Task.Run(() => MethodWithParameter(param)); 

我的解决方案:

为了解决这个问题,我最终编写了一些类似于以下代码的代码:

(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);

这使我可以安全地异步使用该参数,尽管事实上在启动任务后参数变化很快(这导致发布的解决方案出现问题)。

使用这种方法,param(值类型)会传入其值,因此,即使异步方法在param更改后运行,p也将具有运行param该行代码时的值。


5
我热切地等待着谁能想到一种可以更轻松,更轻松地完成此工作的方法。诚然,这很丑陋。
卡登·伯加特

5
您可以在这里:var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Stephen Cleary

1
顺便说一句,斯蒂芬已经在一年半前的回答中讨论过了。
Servy

1
@Servy:实际上,这是Scott的答案。我没有回答这个。
史蒂芬·克利西

斯科特的答案实际上对我不起作用,因为我正在for循环中运行它。本地参数将在下一次迭代中重置。我发布的答案的不同之处在于,参数已复制到lambda表达式的范围中,因此该变量立即安全。用斯科特的答案,该参数仍在同一范围内,因此它仍可以在调用该行和执行异步功能之间进行更改。
卡登·伯加特

5

只需使用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;
}

1
只是要小心闭包,如果这样做的话,其for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }行为就不会rawData像在OP的StartNew示例中传入的那样。
斯科特·张伯伦

@ScottChamberlain-这似乎是一个不同的示例;)我希望大多数人了解有关结束lambda值的知识。
特拉维斯·J

3
如果以前的评论没有意义,请参阅Eric Lipper关于该主题的博客:blogs.msdn.com/b/ericlippert/archive/2009/11/12/…它解释了为什么这种情况发生得很好。
特拉维斯·J

2

目前尚不清楚最初的问题是否与我遇到的问题相同:想要在循环内最大化计算的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();
}

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

1
尽管这可以回答问题,但已将其标记为进行审查。没有解释的答案通常被认为是低质量的。请提供一些评论,说明为什么这是正确的答案。
Dan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.