private void RunAsync()
{
string param = "Hi";
Task.Run(() => MethodWithParameter(param));
}
private void MethodWithParameter(string param)
{
}
编辑
由于大家的需求,我必须注意到启动的Task
将与调用线程并行运行。假设使用默认的TaskScheduler
,这将使用.NET ThreadPool
。无论如何,这意味着您需要考虑传递给Task
的任何参数可能同时被多个线程访问,并使它们成为共享状态。这包括在调用线程上访问它们。
在上面的代码中,这种情况是完全无关紧要的。字符串是不可变的。这就是为什么我把它们作为例子使用的原因。但是,假设您没有使用String
...
一个解决方案是使用async
和await
。这默认情况下将捕获调用线程的SynchronizationContext
,并将在对await
的调用之后为方法的其余部分创建一个延续并将其附加到创建的Task
。如果此方法在WinForms GUI线程上运行,则其类型将为WindowsFormsSynchronizationContext
。
延续将在被发送回捕获的SynchronizationContext
后运行——同样仅限默认情况下。因此,在await
调用之后,您将回到开始的线程上。您可以以各种方式更改这一点,特别是使用ConfigureAwait
。简而言之,该方法的其余部分将在Task
在另一个线程上完成之后才会继续运行。但是,调用线程将继续并行运行,只有该方法的其余部分不会。
该方法等待完成其余部分可能是可取的,也可能不是。如果该方法中没有后来访问传递给Task
的参数,您可能根本不想使用await
。
或者,您稍后在方法中使用这些参数。没有理由立即使用await
,因为您可以安全地继续执行工作。请记住,您可以将返回的Task
存储在变量中,并稍后在同一方法中await
它——甚至在需要在执行一些其他工作后安全访问传递的参数时。再次强调,您不需要在运行Task
时立即使用await
。
无论如何,使传递给Task.Run
的参数在线程安全方面变得简单的方法是这样做:
您必须首先使用async
修饰RunAsync
:
private async void RunAsync()
重要提示
如链接文档所述,最好使用标记为async
的方法不应该返回 void。常见的例外是事件处理程序(如按钮点击等),它们必须返回 void。否则,我总是尝试在使用 async
时返回 Task
或 Task<TResult>
。这是一个很好的实践,有很多原因。
现在,您可以像下面这样await
运行Task
。您不能在没有async
的情况下使用await
。
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
等待任务,则可以避免将传入参数视为可能共享资源的情况,并避免多个线程同时修改某些内容的所有陷阱。此外,请注意闭包。我不会深入介绍它们,但链接的文章做得非常好。
关于 Run
和 StartNew
,下面的代码是我认为最重要的要知道的。有合法的理由可选择其中任一种,两者都不过时或比另一种更好。只有在了解以下内容时,才能简单地用一个替换另一个:
//These are exactly the same
Task.Run(x);
Task.Factory.StartNew(x, CancellationToken.None,
TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
//These are also exactly the same
Task.Factory.StartNew(x);
Task.Factory.StartNew(x, CancellationToken.None,
TaskCreationOptions.None, TaskScheduler.Current);
侧记
话题有点偏离,但是由于WinForms GUI线程被标记为[STAThread]
,注意不要在此线程上使用任何类型的“阻塞”。使用await
完全不会阻塞,但我有时候会看到它与某种形式的阻塞一起使用。
“阻塞”使用引号因为你在技术上不能阻塞WinForms GUI线程。即使在WinForms GUI线程上使用lock
,它仍然会传递消息,尽管您认为它已经“阻塞”了。它没有。
这可能会在非常罕见的情况下导致奇怪的问题。例如,其中一个原因是你永远不想在绘图时使用lock
。但那是一个边缘和复杂的案例;但我曾看到它引起疯狂的问题。所以我为了完整起见而指出了它。
rawData
是一个网络数据包,它有一个容器类(比如 DataPacket),我正在重用这个实例来减少垃圾回收压力。因此,如果我直接在Task
中使用rawData
,它可能会在Task
处理之前被改变。现在,我认为可以为它创建另一个byte[]
实例。我认为这是对我来说最简单的解决方案。 - Thus Spoke NomadAction<byte[]>
并不会改变这一点。 - Jon Skeet