.Result operation doesn't block - c#

I expect the following code to be blocked for almost 5 secs, but it is not. It immediately prints. Isn't Result operation blocking?
class Program
{
static void Main()
{
// Return a value type with a lambda expression
Task<int> task1 = Task<int>.Factory.StartNew(() => Task.Delay(5000).Id + 100);
int i = task1.Result;
Console.WriteLine(i);
}
}

This code is not waiting for Delay to finish. It starts the delay and then return immediately Id+100. So, when the code reaches the Result operation, the task1 is almost always is in Completed state, so you get Result immediately.
You can try following to get desired behaviour
Task<int> task1 = Task.Run(async () => await Task.Delay(5000).Id + 100);
int i = task1.Result;
Or better, use await instead of Result everywhere
Task<int> task1 = Task.Run(async () => await Task.Delay(5000).Id + 100);
int i = await task1;
or even
int i = await Task.Delay(5000).Id + 100
(but I'm unsure here as you may have more logic inside the task in actual code)

To me ContinueWith seems more natural for what you're trying to do:
Task<int> t = Task.Delay(5000).ContinueWith(delayTask => delayTask.Id + 100);
Console.WriteLine(t.Result);
This will execute the delay task and then execute the lambda in ContinueWith after the delay is complete.

Related

Is there a way to wait for all tasks until a specific result is true, and then cancel the rest?

In my C# console application, I'm trying to run multiple tasks that do various data checks simultaneously. If one of the tasks returns true I should stop the other tasks since I have my actionable result. It's also very possible none of the functions return true
I have the code to run the tasks together (I think), I'm just having trouble getting to the finish line:
Task task1 = Task.Run(() => Task1(stoppingToken));
Task task2 = Task.Run(() => Task2(stoppingToken));
Task task3 = Task.Run(() => Task3(stoppingToken));
Task task4 = Task.Run(() => Task4(stoppingToken));
Task task5 = Task.Run(() => Task5(stoppingToken));
Task task6 = Task.Run(() => Task6(stoppingToken));
Task.WaitAll(task1, task2, task3, task4, task5, task6);
This is a little different than the answer in the linked question where the desired result is known (timeout value). I'm waiting for any of these tasks to possibly return true and then cancel the remaining tasks if they are still running
Task.WhenAny with cancellation of the non completed tasks and timeout
Here's a solution based on continuation tasks. The idea is to append continuation tasks to each of the original (provided) tasks, and check the result there. If it's a match, the completion source will be set with a result (if there's no match, the result won't be set at all).
Then, the code will wait for whatever happens first: either all the continuation tasks complete, or the task completion result will be set. Either way, we'll be ready to check the result of the task associated with task completion source (that's why we wait for the continuation tasks to complete, not the original tasks) and if it's set, it's pretty much an indication that we have a match (the additional check at the end is a little paranoid, but better safe than sorry I guess... :D)
public static async Task<bool> WhenAnyHasResult<T>(Predicate<T> isExpectedResult, params Task<T>[] tasks)
{
const TaskContinuationOptions continuationTaskFlags = TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent;
// Prepare TaskCompletionSource to be set only when one of the provided tasks
// completes with expected result
var tcs = new TaskCompletionSource<T>();
// For every provided task, attach a continuation task that fires
// once the original task was completed
var taskContinuations = tasks.Select(task =>
{
return task.ContinueWith(x =>
{
var taskResult = x.Result;
if (isExpectedResult(taskResult))
{
tcs.SetResult(taskResult);
}
},
continuationTaskFlags);
});
// We either wait for all the continuation tasks to be completed
// (it's most likely an indication that none of the provided tasks completed with the expected result)
// or for the TCS task to complete (which means a failure)
await Task.WhenAny(Task.WhenAll(taskContinuations), tcs.Task);
// If the task from TCS has run to completion, it means the result has been set from
// the continuation task attached to one of the tasks provided in the arguments
var completionTask = tcs.Task;
if (completionTask.IsCompleted)
{
// We will check once more to make sure the result is set as expected
// and return this as our outcome
var tcsResult = completionTask.Result;
return isExpectedResult(tcsResult);
}
// TCS result was never set, which means we did not find a task matching the expected result.
tcs.SetCanceled();
return false;
}
Now, the usage will be as follows:
static async Task ExampleWithBooleans()
{
Console.WriteLine("Example with booleans");
var task1 = SampleTask(3000, true);
var task2 = SampleTask(5000, false);
var finalResult = await TaskUtils.WhenAnyHasResult(result => result == true, task1, task2);
// go ahead and cancel your cancellation token here
Console.WriteLine("Final result: " + finalResult);
Debug.Assert(finalResult == true);
Console.WriteLine();
}
What's nice about putting it into a generic method, is that it works with any type, not only booleans, as a result of the original task.
Assuming your tasks return bool you can do something like this:
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken stoppingToken = source.Token;
Task<bool> task1 = Task.Run(() => Task1(stoppingToken));
....
var tasks = new List<Task<bool>>
{
task1, task2, task3, ...
};
bool taskResult = false;
do
{
var finished = await Task.WhenAny(tasks);
taskResult = finished.Result;
tasks.Remove(finished);
} while (tasks.Any() && !taskResult);
source.Cancel();
You could use an asynchronous method that wraps a Task<bool> to another Task<bool>, and cancels a CancellationTokenSource if the result of the input task is true. In the example below this method is the IfTrueCancel, and it is implemented as local function. This way it captures the CancellationTokenSource, and so you don't have to pass it as argument on every call:
var cts = new CancellationTokenSource();
var stoppingToken = cts.Token;
var task1 = IfTrueCancel(Task.Run(() => Task1(stoppingToken)));
var task2 = IfTrueCancel(Task.Run(() => Task2(stoppingToken)));
var task3 = IfTrueCancel(Task.Run(() => Task3(stoppingToken)));
var task4 = IfTrueCancel(Task.Run(() => Task4(stoppingToken)));
var task5 = IfTrueCancel(Task.Run(() => Task5(stoppingToken)));
var task6 = IfTrueCancel(Task.Run(() => Task6(stoppingToken)));
Task.WaitAll(task1, task2, task3, task4, task5, task6);
async Task<bool> IfTrueCancel(Task<bool> task)
{
bool result = await task.ConfigureAwait(false);
if (result) cts.Cancel();
return result;
}
Another, quite different, solution to this problem could be to use PLINQ instead of explicitly created Tasks. PLINQ requires an IEnumerable of something in order to do parallel work on it, and in your case this something is the Task1, Task2 etc functions that you want to invoke. You could put them in an array of Func<CancellationToken, bool>, and solve the problem this way:
var functions = new Func<CancellationToken, bool>[]
{
Task1, Task2, Task3, Task4, Task5, Task6
};
bool success = functions
.AsParallel()
.WithDegreeOfParallelism(4)
.Select(function =>
{
try
{
bool result = function(stoppingToken);
if (result) cts.Cancel();
return result;
}
catch (OperationCanceledException)
{
return false;
}
})
.Any(result => result);
The advantage of this approach is that you can configure the degree of parallelism, and you don't have to rely on the ThreadPool availability for limiting the concurrency of the whole operation. The disadvantage is that all functions should have the same signature. You could overcome this disadvantage by declaring the functions as lambda expressions like this:
var functions = new Func<CancellationToken, bool>[]
{
ct => Task1(arg1, ct),
ct => Task2(arg1, arg2, ct),
ct => Task3(ct),
ct => Task4(arg1, arg2, arg3, ct),
ct => Task5(arg1, ct),
ct => Task6(ct)
};

The await operator is not waiting like I expected

I am working on a class DelayedExecutor that will delay the execution of an Action passed to its DelayExecute method by a certain time timeout (see code below) using the async and await statements. I also want to be able to abort the execution within the timeout interval if needed. I have written a small test to test its behavior like this:
Code for Test method:
[TestMethod]
public async Task DelayExecuteTest()
{
int timeout = 1000;
var delayExecutor = new DelayedExecutor(timeout);
Action func = new Action(() => Debug.WriteLine("Ran function!"));
var sw = Stopwatch.StartNew();
Debug.WriteLine("sw.ElapsedMilliseconds 1: " + sw.ElapsedMilliseconds);
Task delayTask = delayExecutor.DelayExecute(func);
await delayTask;
Debug.WriteLine("sw.ElapsedMilliseconds 2: " + sw.ElapsedMilliseconds);
Thread.Sleep(1000);
}
}
The output I expected from this test was that it would show me:
sw.ElapsedMilliseconds outside DelayExecute 1: ...
Ran Action!"
sw.ElapsedMilliseconds inside DelayExecute: ...
sw.ElapsedMilliseconds outside DelayExecute 2:
However I get this and do not understand why:
sw.ElapsedMilliseconds outside DelayExecute 1: 0
sw.ElapsedMilliseconds outside DelayExecute 2: 30
Ran Action!
sw.ElapsedMilliseconds inside DelayExecute: 1015
On this blog post I read:
I like to think of “await” as an “asynchronous wait”. That is to say, the async method pauses until the awaitable is complete (so it waits), but the actual thread is not blocked (so it’s asynchronous).
This seems to be inline with my expectation, so what is going on here and where is my error?
Code for DelayedExecutor:
public class DelayedExecutor
{
private int timeout;
private Task currentTask;
private CancellationToken cancellationToken;
private CancellationTokenSource tokenSource;
public DelayedExecutor(int timeout)
{
this.timeout = timeout;
tokenSource = new CancellationTokenSource();
}
public void AbortCurrentTask()
{
if (currentTask != null)
{
if (!currentTask.IsCompleted)
{
tokenSource.Cancel();
}
}
}
public Task DelayExecute(Action func)
{
AbortCurrentTask();
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
return currentTask = Task.Factory.StartNew(async () =>
{
var sw = Stopwatch.StartNew();
await Task.Delay(timeout, cancellationToken);
func();
Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
});
}
}
Update:
As suggested I modified this line inside my DelayExecute
return currentTask = Task.Factory.StartNew(async () =>
into
return currentTask = await Task.Factory.StartNew(async () =>
For this to work I needed to change the signature into this
public async Task<Task> DelayExecute(Action func)
so that my new definition is this:
public async Task<Task> DelayExecute(Action func)
{
AbortCurrentTask();
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
return currentTask = await Task.Factory.StartNew(async () =>
{
var sw = Stopwatch.StartNew();
await Task.Delay(timeout, cancellationToken);
func();
Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
});
}
However now I have the same behavior as before. Is there some way achieve what I am trying to do using my design. Or is it fundamentally flawed?
By the way, I also tried putting await await delayTask; inside my test DelayExecuteTest, but this gives me the error
Cannot await 'void'
This is the updated test-method DelayExecuteTest, which does not compile:
public async Task DelayExecuteTest()
{
int timeout = 1000;
var delayExecutor = new DelayedExecutor(timeout);
Action func = new Action(() => Debug.WriteLine("Ran Action!"));
var sw = Stopwatch.StartNew();
Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 1: " + sw.ElapsedMilliseconds);
Task delayTask = delayExecutor.DelayExecute(func);
await await delayTask;
Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 2: " + sw.ElapsedMilliseconds);
Thread.Sleep(1000);
}
There's a bit going on here so I'm going to post a simple answer to begin with, let's see if it suffices.
You'we wrapped a task inside a task, this needs a double await. Otherwise you're only waiting for the inner task to reach its first return point, which will (can) be at the first await.
Let's look at your DelayExecute method in more detail. Let me begin with changing the return type of the method and we'll see how this changes our perspective. The change of return type is just a clarification. You're returning a Task that is nongeneric right now, in reality you're returning a Task<Task>.
public Task<Task> DelayExecute(Action func)
{
AbortCurrentTask();
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
return currentTask = Task.Factory.StartNew(async () =>
{
var sw = Stopwatch.StartNew();
await Task.Delay(timeout, cancellationToken);
func();
Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
});
}
(note that this will not compile since currentTask is also of type Task, but this is largely irrelevant if you read the rest of the question)
OK, so what happens here?
Let's explain a simpler example first, I tested this in LINQPad:
async Task Main()
{
Console.WriteLine("before await startnew");
await Task.Factory.StartNew(async () =>
{
Console.WriteLine("before await delay");
await Task.Delay(500);
Console.WriteLine("after await delay");
});
Console.WriteLine("after await startnew");
}
When executing this I get this output:
before await startnew
before await delay
after await startnew
after await delay -- this comes roughly half a second after previous line
So why this?
Well, your StartNew returns a Task<Task>. This is not a task that now will wait for the inner task to complete, it waits for the inner task to return, which it will (can) do at its first await.
So let's see the full execution path of this by numbering the interesting lines and then explaining the order in which things happen:
async Task Main()
{
Console.WriteLine("before await startnew"); 1
await Task.Factory.StartNew(async () => 2
{
Console.WriteLine("before await delay"); 3
await Task.Delay(500); 4
Console.WriteLine("after await delay"); 5
});
Console.WriteLine("after await startnew"); 6
}
1. The first Console.WriteLine executes
nothing magical here
2. We spin up a new task with Task.Factory.StartNew and await it.
Here our main method can now return, it will return a task that will continue once the inner task has completed.
The job of the inner task is not to execute all the code in that delegate. The job of the inner task is to produce yet another task.
This is important!
3. The inner task now starts executing
It will essentially execute all the code in the delegate up to and including the call to Task.Delay, which returns a Task.
4. The inner task arrives at its first await
Since this will not already have completed (it will only complete roughly 500ms later), this delegate now returns.
Basically, the await <this task we got from Task.Delay> statement will create a continuation and then return.
This is important!
6. Our outer task now continues
Since the inner task returned, the outer task can now continue. The result of calling await Task.Factory.StartNew is yet another task but this task is just left to fend for itself.
5. The inner task, continues roughly 500ms later
This is now after the outer task has already continued, potentially finished executing.
So in conclusion your code executes "as expected", just not the way you expected.
There are several ways to fix this code, I'm simply going to rewrite your DelayExecute method to the simplest way to do what you want to do:
Change this line:
return currentTask = Task.Factory.StartNew(async () =>
Into this:
return currentTask = await Task.Factory.StartNew(async () =>
This will let the inner task start your stopwatch and reach the first await before returning all the way out of DelayExecute.
The similar fix to my LINQPad example above would be to simply change this line:
await Task.Factory.StartNew(async () =>
To this:
await await Task.Factory.StartNew(async () =>
After giving it some more thought I figured out, how to achieve what I was aiming for, which is to delay the start of an action and be able to abort it in case I need to. Credit goes to Lasse V. Karlsen for helping me understand the issue.
The answer by Lasse is correct for my original problem, which is why I accepted it. But in case somebody needs to achieve something similar to what I needed, here is how I solved it. I had to use a continuation task after the task started with StartNew. I also renamed the method AbortCurrentTask into AbortExecution(). The new version of my DelayedExecutor class is this:
public class DelayedExecutor
{
private int timeout;
private Task currentTask;
private CancellationToken cancellationToken;
private CancellationTokenSource tokenSource;
public DelayedExecutor(int timeout)
{
this.timeout = timeout;
tokenSource = new CancellationTokenSource();
}
public void AbortExecution()
{
if (currentTask != null)
{
if (!currentTask.IsCompleted)
{
tokenSource.Cancel();
}
}
}
public Task DelayExecute(Action func)
{
AbortExecution();
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
return currentTask =
Task.Delay(timeout, cancellationToken).ContinueWith(t =>
{
if(!t.IsCanceled)
{
var sw = Stopwatch.StartNew();
func();
Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
}
});
}
}
This class now does what I expected from it. These two tests I now give the expected output:
[TestMethod]
public async Task DelayExecuteTest()
{
int timeout = 1000;
var delayExecutor = new DelayedExecutor(timeout);
Action func = new Action(() => Debug.WriteLine("Ran Action!"));
var sw = Stopwatch.StartNew();
Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 1: " + sw.ElapsedMilliseconds);
Task delayTask = delayExecutor.DelayExecute(func);
await delayTask;
Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 2: " + sw.ElapsedMilliseconds);
}
Output:
sw.ElapsedMilliseconds outside DelayExecute 1: 0
Ran Action!
sw.ElapsedMilliseconds inside DelayExecute: 3
sw.ElapsedMilliseconds outside DelayExecute 2: 1020
and
[TestMethod]
public async Task AbortDelayedExecutionTest()
{
int timeout = 1000;
var delayExecutor = new DelayedExecutor(timeout);
Action func = new Action(() => Debug.WriteLine("Ran Action!"));
var sw = Stopwatch.StartNew();
Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 1: " + sw.ElapsedMilliseconds);
Task delayTask = delayExecutor.DelayExecute(func);
Thread.Sleep(100);
delayExecutor.AbortExecution();
await delayTask;
Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 2: " + sw.ElapsedMilliseconds);
}
Output:
sw.ElapsedMilliseconds outside DelayExecute 1: 0
sw.ElapsedMilliseconds outside DelayExecute 2: 122

what happens if I await a task that is already running or ran?

There is a Task variable and lets say the task is running right now.. by executing the following line.
await _task;
I was wondering what happens when I write this code:
await _task;
await _task;
would it execute the task twice ? Or throw an exception because it has already run ?
would it execute the task twice ? Or throw an exception because it has
already run ?
No and no. The only thing await does is call Task.GetAwaiter, it does not cause anything to run. If the task already ran to completion, it will either extract the value if it is a Task<T>, or run synchronously to the next line if it is a Task, since there is an optimization for already completed tasks.
A simple demo:
async Task Main()
{
var foo = FooAsync();
await foo;
await foo;
var bar = BarAsync();
var firstResult = await bar;
var secondResult = await bar;
Console.WriteLine(firstResult);
Console.WriteLine(secondResult);
}
public async Task FooAsync()
{
await Task.Delay(1);
}
public async Task<int> BarAsync()
{
await Task.Delay(1);
return 1;
}
If you drill down to the state machine itself, you'll see this:
this.<foo>5__1 = this.<>4__this.FooAsync();
taskAwaiter = this.<foo>5__1.GetAwaiter();
if (!taskAwaiter.IsCompleted)
{
this.<>1__state = 0;
this.<>u__1 = taskAwaiter;
M.<FooBar>d__0 <FooBar>d__ = this;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, M.<FooBar>d__0>
(ref taskAwaiter, ref <FooBar>d__);
return;
}
This optimization first checks the completion of the task. If the task isn't complete, it will call UnsafeOnCompleted which will register the continuation. If it is complete, it breaks the switch and goes to:
this.<>1__state = -2;
this.<>t__builder.SetResult();
Which sets the result for the underlying Task, and that way you actually get the value synchronously.
I threw together this quick test:
var i = 0;
var task = Task.Run(() => { i++; Console.WriteLine(i); return i; });
var x = await task;
var y = await task;
Console.WriteLine(x);
Console.WriteLine(y);
It writes:
1
1
1
So, clearly, the task only runs once.

Serializing async task and async continuation

I have long running processing that I want to perform in a background task. At the end of the task, I want to signal that it has completed. So essentially I have two async tasks that I want to run in the background, one after the other.
I am doing this with continuations, but the continuation is starting prior to the initial task completing. The expected behavior is that the continuation run only after the initial task has completed.
Here is some sample code that demonstrates the problem:
// Setup task and continuation
var task = new Task(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
});
task.Start();
The DebugLog function:
static void DebugLog(string s, params object[] args)
{
string tmp = string.Format(s, args);
System.Diagnostics.Debug.WriteLine("{0}: {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), tmp);
}
Expected Output:
TASK Starting
TASK Finishing
CONTINUATION Starting
CONTINUATION Ending
Actual Output:
TASK Starting
CONTINUATION Starting
CONTINUATION Ending
TASK Finishing
Again, my question is why is the continuation starting prior to the completion of the initial task? How do I get the continuation to run only after the completion of the first task?
Workaround #1
I can make the above code work as expected if I make the intial task synchronous - that is if I Wait on the Task.Delay like so:
var task = new Task(() =>
{
DebugLog("TASK Starting");
Task.Delay(1000).Wait(); // Wait instead of await
DebugLog("TASK Finishing");
});
For many reasons, it is bad to use Wait like this. One reason is that it blocks the thread, and this is something I want to avoid.
Workaround #2
If I take the task creation and move it into it's own function, that seems to work as well:
// START task and setup continuation
var task = Test1();
task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
});
static public async Task Test1()
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
}
Credit for the above approach goes to this somewhat related (but not duplicate) question: Use an async callback with Task.ContinueWith
Workaround #2 is better than Workaround #1 and is likely the approach I'll take if there is no explanation for why my initial code above does not work.
Your original code is not working because the async lambda is being translated into an async void method underneath, which has no built-in way to notify any other code that it has completed. So, what you're seeing is the difference between async void and async Task. This is one of the reasons that you should avoid async void.
That said, if it's possible to do with your code, use Task.Run instead of the Task constructor and Start; and use await rather than ContinueWith:
var task = Task.Run(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
await task;
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
Task.Run and await work more naturally with async code.
You shouldn't be using the Task constructor directly to start tasks, especially when starting async tasks. If you want to offload work to be executed in the background use Task.Run instead:
var task = Task.Run(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
About the continuation, it would be better to just append it's logic to the end of the lambda expression. But if you're adamant on using ContinueWith you need to use Unwrap to get the actual async task and store the it so you could handle exceptions:
task = task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
}).Unwrap();
try
{
await task;
}
catch
{
// handle exceptions
}
Change your code to
// Setup task and continuation
var t1 = new Task(() =>
{
Console.WriteLine("TASK Starting");
Task.Delay(1000).Wait(); // actual work here
Console.WriteLine("TASK Finishing");
});
var t2 = t1.ContinueWith((x) =>
{
Console.WriteLine("C1");
Task.Delay(100).Wait(); // actual work here
Console.WriteLine("C2");
});
t1.Start();
// Exception will be swallow without the line below
await Task.WhenAll(t1, t2);

.Continue starts before task is completed

I have the following code in C#, VS2012, WPF 4.5.
My expectation would be that, .ContinueWith will be executed after the task has finished completely (that's a continuation's whole purpose, isn't it?).
This should result in a value of 2 in finalResult.
int myTestInt = 0;
Task task = Task.Factory.StartNew(async () =>
{
myTestInt = 1;
await Task.Delay(TimeSpan.FromSeconds(6));
myTestInt = 2;
}).ContinueWith(_ =>
{
int finalResult = myTestInt;
});
In fact, finalResult is assigned a value of 1 instead. So it seems like the continuation is started on the await statement already.
Is this the intended behaviour? Am I missing something here? Can't I rely on ContinueWithto start after a task has completely finished?
Update:
Justin's answer just inspired me to check the following:
int myTestInt = 0;
Task task=Task.Factory.StartNew(async () =>
{
myTestInt = 1;
await Task.Delay(TimeSpan.FromSeconds(6));
myTestInt = 2;
});
task.Wait();
int result2 = myTestInt;
finalResult is still set to 1. Is there no way to reliably wait for a task that contains awaits to complete?
When you pass an async delegate to Task.Factory.StartNew, the returned Task only represents the first portion of that delegate (up until the time it awaits something that is not already completed).
However, if you pass an async delegate to the new Task.Run method (which was included for this reason), the returned Task represents the entire delegate. So you can use ContinueWith as you expect. (Although await is usually a better option than ContinueWith).
For more information on StartNew vs Run, see Stephen Toub's post on the topic.
The await will immediately return control to the calling function, which in this case is the StartNew of your task. This means the task will then complete and execute the ContinueWith.
If you really want task to complete before the ContinueWith, then don't await the Task.Delay.
I saw this in the MSDN: :-)
public async void button1_Click(object sender, EventArgs e)
{
pictureBox1.Image = await Task.Run(async() =>
{
using(Bitmap bmp1 = await DownloadFirstImageAsync())
using(Bitmap bmp2 = await DownloadSecondImageAsync())
return Mashup(bmp1, bmp2);
});
}
So do not forget the "async()" !!!

Categories

Resources