Task.Run with Parameter(s)?

MFa*_*MAR 71 c# lambda task task-parallel-library

我正在开展一个多任务网络项目,我是新手Threading.Tasks.我实现了一个简单的Task.Factory.StartNew(),我想知道我该怎么做Task.Run()

这是基本代码:

Task.Factory.StartNew(new Action<object>(
(x) =>
{
    // Do something with 'x'
}), rawData);
Run Code Online (Sandbox Code Playgroud)

System.Threading.Tasks.Task对象浏览器中查看,我找不到Action<T>类似的参数.只有Action这需要void参数,没有类型.

只有两件事情similiar:static Task Run(Action action)static Task Run(Func<Task> function),但不能发布与两个参数(一个或多个).

是的,我知道我可以,但创建一个简单的扩展方法,我的主要问题是,我们可以把它写在一行Task.Run()

Zer*_*er0 93

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

private void MethodWithParameter(string param)
{
    //Do stuff
}
Run Code Online (Sandbox Code Playgroud)

编辑

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

在上面的代码中,案例完全没有实际意义.字符串是不可变的.这就是我用它们作为例子的原因.但是说你没有使用String......

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

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

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

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

无论如何,关于传递给参数的这个线程安全的简单方法Task.Run是这样做:

你必须先装饰RunAsyncasync:

private async void RunAsync()
Run Code Online (Sandbox Code Playgroud)

重要的提示

优选地,标记的方法不应返回无效,如链接文档所述.常见的例外是事件处理程序,例如按钮点击等.他们必须归还无效.否则我总是尝试返回或使用时.出于好几个原因,这是一种很好的做法.async TaskTask<TResult>async

现在你可以awaitTask下面运行.你不能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
Run Code Online (Sandbox Code Playgroud)

因此,通常情况下,如果您await执行任务,则可以避免将传入的参数视为可能的共享资源,同时具有从多个线程修改某些内容的所有缺陷.另外,要注意关闭.我不会深入介绍那些,但链接的文章做得很好.

边注

有点偏离主题,但要小心使用WinForms GUI线程上的任何类型的"阻塞",因为它被标记[STAThread].使用await不会阻塞,但有时我会看到它与某种阻塞一起使用.

"Block"在引号中,因为您在技术上无法阻止WinForms GUI线程.是的,如果你使用lock上的WinForms GUI线程它仍然抽取消息,尽管你认为它的"封杀".不是.

在非常罕见的情况下,这可能会导致奇怪的问题.lock例如,你永远不想使用绘画的原因之一.但这是一个边缘而复杂的案例; 但是我看到它会导致疯狂的问题.所以我完整地注意到它.

  • 你没有等待`Task.Run(()=> MethodWithParameter(param));`.这意味着如果在``Task.Run`之后``param`被修改**,你可能在`MethodWithParameter`上有意想不到的结果. (16认同)
  • 如果这是错误的,为什么这是一个公认的答案.它完全不等同于传递状态对象. (5认同)
  • @Zer0状态对象是Task.Factory.StartNew https://msdn.microsoft.com/en-us/library/dd321456(v=vs.110).aspx中的第二个参数,它保存了对象的值调用StartNew的那一刻,你的答案创建了一个闭包,它保留了引用(如果param的值在任务运行之前发生了变化,它也会在任务中发生变化),所以你的代码根本不等于什么问题是问.答案实际上是没有办法用Task.Run()编写它. (4认同)
  • @Zer0 也许 *你* 应该阅读源代码。一个传递状态对象,另一个不传递。这就是我从一开始就说的。Task.Run **不是**Task.Factory.StartNew 的简写。出于遗留原因,状态对象版本存在,但它仍然存在并且有时行为不同,因此人们应该意识到这一点。 (3认同)
  • 阅读 Toub 的文章,我将重点强调这句话“您可以使用接受对象状态的重载,对于性能敏感的代码路径,可以使用它来避免闭包和相应的分配”。我认为这就是 @Zero 在考虑 Task.Run over StartNew 用法时所暗示的。 (3认同)
  • @Zer0 用于带有闭包的结构 Task.Run 和带有第二个参数的 Task.Factory.StartNew(这与每个链接的 Task.Run 不同)*将*表现不同,因为在后一种情况下将制作一个副本。我的错误是在原始评论中泛指对象,我的意思是它们不完全等效。 (2认同)
  • 我的天啊。这是我见过的一个简单问题的 TL'DR-est 解释,这通常表明没有得到概念,因此无法简单地阐明它。也就是说,这个例子不好,所以解释为什么它不好(不是线程安全的)比仅仅提供一个工作例子或者删除答案更不理想。 (2认同)

Sco*_*ain 28

使用变量捕获来"传入"参数.

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

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

  • +1考虑到在调用`Task.Run`之后可能立即更改变量的重要事实. (8认同)
  • 这将如何帮助?如果在任务线程中使用 x ,并且 x 是对对象的引用,并且在任务线程运行的同时修改对象,则会导致严重破坏。 (2认同)

Arn*_* F. 12

从现在开始,您还可以:

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

  • 这是最好的答案,因为它允许传入状态,并防止 [Kaden Burgart 的答案](/sf/answers/2802097861/) 中提到的可能情况。例如,如果您需要将“IDisposable”对象传递到任务委托中以解决 ReSharper 警告“捕获的变量已在外部作用域中释放”,这可以很好地实现。与流行的看法相反,在需要传递状态的地方使用“Task.Factory.StartNew”而不是“Task.Run”并没有什么问题。请参阅[此处](https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/)。 (3认同)

Tra*_*s J 5

只需使用 Task.Run

var task = Task.Run(() =>
{
    //this will already share scope with rawData, no need to use a placeholder
});
Run Code Online (Sandbox Code Playgroud)

或者,如果您想在方法中使用它并稍后等待任务

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 如果之前的评论没有任何意义,请参阅 Eric Lipper 关于该主题的博客:http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closure-over-the-loop-variable-考虑-有害.aspx 它解释了为什么这种情况发生得很好。 (3认同)
  • 如果你这样做的话,要小心闭包 `for(int rawData = 0; rawData &lt; 10; ++rawData) { Task.Run(() =&gt; { Console.WriteLine(rawData); } ) }` 不会行为与在 OP 的 StartNew 示例中传入“rawData”相同。 (2认同)

Kad*_*art 5

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

问题:

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

Task.Run(() => MethodWithParameter(param)); 
Run Code Online (Sandbox Code Playgroud)

我的解决方案:

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

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

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

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

  • 我热切地等待着谁能想到一种可以更轻松,更轻松地完成此工作的方法。诚然,这很丑陋。 (4认同)
  • 在这里,您可以:`var localParam = param; 等待Task.Run(()=&gt; MethodWithParam(localParam)); (4认同)
  • 顺便说一下,一年半前,斯蒂芬已经在他的回答中讨论过。 (2认同)