Task.Run with Parameter(s)?

时间:2015-05-13 21:27:34

标签: c# lambda task-parallel-library task

我正在开展一个多任务网络项目,我是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()在单行上写吗?

7 个答案:

答案 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 ......

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

继续将在回发到捕获的SynchronizationContext后运行 - 仅在默认情况下再次运行。因此,您将在await电话后回到您开始使用的主题。您可以通过各种方式更改此设置,尤其是使用ConfigureAwait。简而言之,该方法的其余部分将不会继续,直到之后 Task已在另一个线程上完成。但调用线程将继续并行运行,而不是方法的其余部分。

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

或许您可以在稍后的方法中使用这些参数。没有理由立即await因为你可以继续安全地工作。请记住,您可以将变量中返回的Taskawait存储在其中 - 即使是使用相同的方法。例如,一旦你做了一些其他的工作后,你需要安全地访问传递的参数。同样,当您运行它时,需要在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));
}