使用参数运行? ?

我正在做一个多任务网络项目,我是 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()

193737 次浏览
private void RunAsync()
{
//Beware of closures.  String is immutable.
string param = "Hi";
Task.Run(() => MethodWithParameter(param));
}


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

Edit

Due to popular demand I must note that the Task launched will run in parallel with the calling thread. Assuming the default TaskScheduler this will use the .NET ThreadPool. Anyways, this means you need to account for whatever parameter(s) being passed to the Task as potentially being accessed by multiple threads at once, making them shared state. This includes accessing them on the calling thread.

In my above code that case is made entirely moot. Strings are immutable. That's why I used them as an example. But say you're not using a String...

One solution is to use async and await. This, by default, will capture the SynchronizationContext of the calling thread and will create a continuation for the rest of the method after the call to await and attach it to the created Task. If this method is running on the WinForms GUI thread it will be of type WindowsFormsSynchronizationContext.

The continuation will run after being posted back to the captured SynchronizationContext - again only by default. So you'll be back on the thread you started with after the await call. You can change this in a variety of ways, notably using ConfigureAwait. In short, the rest of that method will not continue until after the Task has completed on another thread. But the calling thread will continue to run in parallel, just not the rest of the method.

This waiting to complete running the rest of the method may or may not be desirable. If nothing in that method later accesses the parameters passed to the Task you may not want to use await at all.

Or maybe you use those parameters much later on in the method. No reason to await immediately as you could continue safely doing work. Remember, you can store the Task returned in a variable and await on it later - even in the same method. For instance, once you need to access the passed parameters safely after doing a bunch some other work. Again, you do not need to await on the Task right when you run it.

Anyways, a simple way to make this thread-safe with respect to the parameters passed to Task.Run is to do this:

You must first decorate RunAsync with async:

private async void RunAsync()

Important Notes

Preferably the method marked async should not return void, as the linked documentation mentions. The common exception to this is event handlers such as button clicks and such. They must return void. Otherwise I always try to return a Task or Task<TResult> when using async. It's good practice for a quite a few reasons.

Now you can await running the Task like below. You cannot use await without 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

So, in general, if you await the task you can avoid treating passed in parameters as a potentially shared resource with all the pitfalls of modifying something from multiple threads at once. Also, beware of closures. I won't cover those in depth but the linked article does a great job of it.

Regarding Run and StartNew the code below I find most important to know, really. There are legitimate reasons to use either, neither is obsolete or "better" than the other. Be aware simply replacing one with the other is a very bad idea unless you understand this:

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

Side Notes

A bit off topic, but be careful using any type of "blocking" on the WinForms GUI thread due to it being marked with [STAThread]. Using await won't block at all, but I do sometimes see it used in conjunction with some sort of blocking.

"Block" is in quotes because you technically cannot block the WinForms GUI thread. Yes, if you use lock on the WinForms GUI thread it will still pump messages, despite you thinking it's "blocked". It's not.

This can cause bizarre issues in very rare cases. One of the reasons you never want to use a lock when painting, for example. But that's a fringe and complex case; however I've seen it cause crazy issues. So I noted it for completeness sake.

Use variable capture to "pass in" parameters.

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

You also could use rawData directly but you must be careful, if you change the value of rawData outside of a task (for example a iterator in a for loop) it will also change the value inside of the task.

Just use Task.Run

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

Or, if you would like to use it in a method and await the task later

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

From now you can also :

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

I know this is an old thread, but I wanted to share a solution I ended up having to use since the accepted post still has an issue.

The Issue:

As pointed out by Alexandre Severino, if param (in the function below) changes shortly after the function call, you might get some unexpected behavior in MethodWithParameter.

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

My Solution:

To account for this, I ended up writing something more like the following line of code:

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

This allowed me to safely use the parameter asynchronously despite the fact that the parameter changed very quickly after starting the task (which caused issues with the posted solution).

Using this approach, param (value type) gets its value passed in, so even if the async method runs after param changes, p will have whatever value param had when this line of code ran.

It's unclear if the original problem was the same problem I had: wanting to max CPU threads on computation inside a loop while preserving the iterator's value and keeping inline to avoid passing a ton of variables to a worker function.

for (int i = 0; i < 300; i++)
{
Task.Run(() => {
var x = ComputeStuff(datavector, i); // value of i was incorrect
var y = ComputeMoreStuff(x);
// ...
});
}

I got this to work by changing the outer iterator and localizing its value with a 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();
}

Idea is to avoid using a Signal like above. Pumping int values into a struct prevents those values from changing (in the struct). I had the following Problem: loop var i would change before DoSomething(i) was called (i was incremented at end of loop before ()=> DoSomething(i,ii) was called). With the structs it doesn't happen anymore. Nasty bug to find: DoSomething(i, ii) looks great, but never sure if it gets called each time with a different value for i (or just a 100 times with i=100), hence -> 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));
}

There is another way of doing this. I found it useful.

int param;
ThreadPool.QueueUserWorkItem(someMethod, param);
void someMethod(object parameter){
var param = (int) parameter;
// do the job
}