112

I have implemented a simple Task.Factory.StartNew() and I wonder how can I do it with Task.Run() instead?

Here is the basic code:

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

I looked into System.Threading.Tasks.Task in Object Browser and I couldn't find a Action<T> like parameter. There is only Action that takes void parameter and no type.

There are only 2 things similiar: static Task Run(Action action) and static Task Run(Func<Task> function) but can't post parameter(s) with both.

Yes, I know I can create a simple extension method for it but my main question is can we write it on single line with Task.Run()?

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
Thus Spoke Nomad
  • 2,372
  • 4
  • 17
  • 34
  • It's not clear what you'd want the *value* of the parameter to be. Where would it come from? If you've already got it, just capture it in the lambda expression... – Jon Skeet May 13 '15 at 21:33
  • @JonSkeet `rawData` is a network data packet that has a container class (like DataPacket) and I'm re-using this instance to reduce GC pressure. So, if I use `rawData` directly in `Task`, it can (probably) be changed before `Task` handles it. Now, I think I can create another `byte[]` instance for it. I think it's the simplest solution for me. – Thus Spoke Nomad May 13 '15 at 21:54
  • Yes, if you need to clone the byte array, you clone the byte array. Having an `Action` doesn't change that. – Jon Skeet May 14 '15 at 05:49
  • [Here](https://stackoverflow.com/a/59799778/5935112) are some **good solutions** to pass parameters to a task. – Just Shadow Jan 18 '20 at 10:54

8 Answers8

150
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.

Zer0
  • 7,191
  • 1
  • 20
  • 34
  • 29
    You are not awaiting `Task.Run(() => MethodWithParameter(param));`. Which means that if `param` gets modified **after** the `Task.Run`, you might have unexpected results on the `MethodWithParameter`. – Alexandre Severino May 13 '15 at 21:43
  • 1
    @Kilouco Of course. That's the expected behavior when passing by reference. Same would happen if you did `ThreadPool.QueueUserWorkItem(x => { MethodWithParameter((string)x); }, param);` Welcome to threading, don't shoot yourself in the foot... – Zer0 May 13 '15 at 21:50
  • I'm just suggesting giving OP a heads-up about this risk. – Alexandre Severino May 13 '15 at 21:52
  • I'm accepting answer because of clear/simple implementation. There is no `Task.Run(Action)` like method to call. Thanks! – Thus Spoke Nomad May 13 '15 at 21:56
  • 14
    Why is this an accepted answer when it's wrong. It's not at all equivalent of passing state object. – Egor Pavlikhin Apr 25 '17 at 22:21
  • 8
    @Zer0 a state object is the second paremeter in Task.Factory.StartNew https://msdn.microsoft.com/en-us/library/dd321456(v=vs.110).aspx and it saves the value of the object at the moment of the call to StartNew, while your answer creates a closure, which keeps the reference (if the value of param changes before the task is run it will also change in the task), so your code is not at all equivalent to what the question was asking. The answer really is that there is no way to write it with Task.Run(). – Egor Pavlikhin Apr 29 '17 at 18:35
  • @EgorPavlikhin - What? `Task.Run` is short-hand for `Task.Factory.StartNew`. They are quite literally [one in the same](https://blogs.msdn.microsoft.com/pfxteam/2011/10/24/task-run-vs-task-factory-startnew/). I want this crystal clear so no one gets misinformation from your comment. Modifying an object you pass to `Task.Factory.StartNew` is **not** thread-safe. It does _not_ create a copy. It's just as dangerous as using a closure. You're far beyond the scope of OP's question. Yet again, I answered his simple bolded question correctly. This isn't an in depth class on closures. EOD. – Zer0 May 04 '17 at 07:09
  • @EgorPavlikhin - For you, and others, [see here](http://stackoverflow.com/questions/22550777/about-the-task-startnewactionobject-object-method) for a better discussion of that particular overload. Totally off-topic but it's more in depth. Outside the scope of this question and a better place to look. Closures are something I'm not going into here. But you can get tripped up by them regardless if you're using `StartNew` or `Task.Run` as they do the same thing, one is simply short-hand. – Zer0 May 04 '17 at 07:18
  • 2
    @Zer0 for structs Task.Run with closure and Task.Factory.StartNew with 2nd parameter (which is not the same as Task.Run per your link) *will* behave differently, since in the latter case a copy will be made. My mistake was in referring to objects in general in the original comment, what I meant was that they are not fully equivalent. – Egor Pavlikhin May 13 '17 at 22:56
  • @EgorPavlikhin Still incorrect. `Task.Run` IS absolutely implemented the same way. Did you not read the link I included? Perhaps you should read the actual source code? You are discussing closures, period. Common language feature has no ties with the TPL in any way. Again... closures are a big part of C# and not at all something unique to `Task.Run`. You can use the synonymous short hand method _without_ closures too... Lastly closures are dangerous for _both_ reference types and structs. So you're off there as well. End of discussion. – Zer0 May 14 '17 at 06:07
  • 3
    @Zer0 Perhaps *you* should read the source code. One passes the state object, the other one doesn't. Which is what I said from the begining. Task.Run is **not** a short-hand for Task.Factory.StartNew. The state object version is there for legacy reasons, but it is still there and it behaves differently sometimes, so people should be aware of that. – Egor Pavlikhin May 19 '17 at 21:15
  • 2
    OMG. That's the TL'DR-est explanation of a simple question I've seen yet, and that's usually an indication of not getting the concept therefore not being able to articulate it simply. That said, the example is no good, so explaining why it's no good (not thread safe) is less desirable than just providing a working example or maybe deleting the answer. – Rick O'Shea Oct 26 '18 at 19:13
  • @Zer0 if am doing this, it works fine Task.Run(() => Myfunc(Param1, Param2, Param3, Param4)); but i want to pass the token and taskcreationoptions, so if i do as below i get many errors Task.Run(() => Myfunc(Param1, Param2, Param3, Param4),mycancellationtoken,TaskCreationOptions.LongRunning); – Harry Nov 05 '18 at 15:59
  • @Harry Task.Run does not support specifying your own task creation options. It defaults to DenyChildAttach (not None). Use Task.Factory.StartNew instead. – Zer0 Dec 11 '18 at 02:58
  • 3
    Reading Toub's article I will highlight this sentence " You get to use overloads that accept object state, which for performance-sensitive code paths can be used to avoid closures and the corresponding allocations". I think this is what @Zero is implying when considering Task.Run over StartNew usage. – davidcarr Feb 05 '19 at 21:27
  • In case anyone else wants to see the difference that @Zer0 pointed out - try `for (int i = 1; i < 20; i++) { Task.Run(() => Console.Out.WriteLine(i)); }` vs `for (int i = 1; i < 20; i++) { Task.Factory.StartNew((j) => Console.Out.WriteLine(j), i); }` – Paul B. Nov 30 '21 at 20:02
39

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.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 11
    +1 for taking into consideration the important fact that the variable might be changed right after calling `Task.Run`. – Alexandre Severino May 13 '15 at 21:46
  • 2
    how is this going to help? if you use x inside the task thread, and x is a reference to an object, and if the object is modified in the same time when the task thread is running it can lead to havoc. – ovi Jul 13 '16 at 10:25
  • 1
    @Ovi-WanKenobi Yes, but that is not what this question was about. It was how to pass a parameter. If you passed a reference to a object as a parameter to a normal function you would have the exact same problem there too. – Scott Chamberlain Jul 13 '16 at 13:28
  • Yup this does not work. My task has no reference back to x in the calling thread. I just get null. – David Price May 02 '19 at 14:38
  • Scott Chamberlain, Passing argument via capture comes with its own issues. In particular, there is the issue of memory leaking and memory pressure. In particular when you try to scale up. (see "8 Ways You can Cause Memory Leaks" for more details). – RashadRivera Dec 06 '20 at 18:41
15

From now you can also :

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)
Arnaud F.
  • 8,252
  • 11
  • 53
  • 102
  • 3
    This is the best answer as it allows a state to be passed in, and prevents the possible situation mentioned in [Kaden Burgart's answer](https://stackoverflow.com/a/40029969/197591). For example, if you need to pass a `IDisposable` object into the task delegate to resolve the ReSharper warning _"Captured variable is disposed in the outer scope"_, this does it very nicely. Contrary to popular belief, there's nothing wrong with using `Task.Factory.StartNew` instead of `Task.Run` where you need to pass state. See [here](https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/). – Neo Nov 14 '20 at 21:00
  • 1
    While this is good a point direction what to do, but the example above will not compile. `StartNew` need `Action` as parameter... – Kirsan Feb 09 '22 at 11:05
  • 1
    @Kirsan I got this to compile by changing the signature of Action to Action in .Net 6. – Jason D Aug 28 '22 at 19:19
10

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.

Kaden Burgart
  • 161
  • 1
  • 6
  • 8
    I eagerly await anyone who can think of a way to do this more legibly with less overhead. This is admittedly rather ugly. – Kaden Burgart Oct 13 '16 at 20:07
  • 6
    Here you go: `var localParam = param; await Task.Run(() => MethodWithParam(localParam));` – Stephen Cleary Oct 13 '16 at 20:11
  • 2
    Which, by the way, Stephen already discussed in his answer, a year and a half ago. – Servy Oct 13 '16 at 20:33
  • 1
    @Servy: That was [Scott's answer](http://stackoverflow.com/a/30225556/263693), actually. I didn't answer this one. – Stephen Cleary Oct 13 '16 at 21:26
  • Scott's answer would not have worked for me actually, as I was running this in a for loop. The local param would have been reset in the next iteration. The difference in the answer I posted is that the param gets copied into the scope of the lambda expression, so the variable is immediately safe. In Scott's answer, the parameter is still in the same scope, so it could still change between calling the line, and executing the async function. – Kaden Burgart Oct 18 '16 at 19:30
4

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;
}
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • 2
    Just be careful of closures if you do it that way `for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }` will not behave the same as if `rawData` was passed in like in the OP's StartNew example. – Scott Chamberlain May 13 '15 at 21:37
  • @ScottChamberlain - That seems like a different example ;) I would hope most people understand about closing over lambda values. – Travis J May 13 '15 at 21:39
  • 3
    And if those previous comments made no sense, please see Eric Lipper's blog on the topic: http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx It explains why this happens very well. – Travis J May 13 '15 at 21:46
4

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();
}
Harald J
  • 49
  • 1
2

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));
}
CodeDigger
  • 199
  • 2
  • 10
  • 2
    While this may answer the question, it was flagged for review. Answers with no explanation are often considered low-quality. Please provide some commentary for why this is the correct answer. – Dan Mar 25 '20 at 17:06
0

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
}
AminSojoudi
  • 1,904
  • 2
  • 18
  • 42