The await operator is not waiting like I expected - c#

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

Related

.Result operation doesn't block

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.

C# Task.Run does not await action variable

See example codes.
Console.WriteLine("start")
Task.Run(async () =>
{
await Task.Delay(3000);
});
Console.WriteLine("end");
// result
// start [3s delay] end
It works!
but below code is not working.
Action action = async () =>
{
await Task.Delay(3000);
};
Console.WriteLine("start")
Task.Run(action);
Console.WriteLine("end");
// result
// start [without delay] end
Why Task.Run does not await async action variable?
edit -------------------------
I'm so sorry. I wrote wrong code.
This is right code.
I test it on C# Interactive of VS 2017
Console.WriteLine("start");
await Task.Run(async () =>
{
await Task.Delay(3000);
});
Console.WriteLine("end");
Action action = async () =>
{
await Task.Delay(3000);
};
Console.WriteLine("start");
await Task.Run(action);
Console.WriteLine("end");
The main reason to use async and Tasks, is to make two or more methods to run simultaneously. So your method will continue running and when finished the delay, will call anything after the delay since you run another async func in a task.
If you run this method, You'll notice
finished
will be printed in the middle of the other work ( and here the other work is for loop,
Console.WriteLine("start");
Task.Run(async () =>
{
await Task.Delay(200);
Console.WriteLine("Finished");
});
for(int i = 0; i < 500; i++)
{
Console.WriteLine(i);
}
Console.WriteLine("end");
Console.ReadKey();
For some reason, It doesn't work as expected on dotnetfiddle
Even if you didn't run async in the task, they will run simultaneously.
Look at this example :
Console.WriteLine("start");
Task.Run(() =>
{
Task.Delay(2000).Wait(); // Will NOT wait !
Console.WriteLine("Finished");
});
for(int i = 0; i < 500; i++)
{
Console.WriteLine(i);
}
Console.WriteLine("end");
Console.ReadKey();
It'll continue without any delay since it's on a Task
But if you want the method to wait, First : Don't run it on a Task.
Second : call the Wait() method.
Like :
Console.WriteLine("start");
Task.Delay(2000).Wait(); // It'll wait for 2 seconds before continue.
for(int i = 0; i < 500; i++)
{
Console.WriteLine(i);
}
Console.WriteLine("end");
Console.ReadKey();
When using Task.Run without an await, this will just 'fire and forget'. Code execution will not wait for it, and it will simply run it as soon as a thread is available from the thread pool.
Typically, if you want to off load some work and wait for that work to be finished, create a separate method that returns a Task, then await that.
Example:
Console.WriteLine("start")
await DoWorkAsync();
Console.WriteLine("end");
/////////
private Task DoWorkAsync()
{
return Task.Run(async () =>
{
await Task.Delay(3000);
});
}
Note that wrapping a method body into a Task and returning it, is not suggested, and it typically leads to a bit of a code smell.
Stephen Cleary has good information about Tasks and async/await. I would read up on this:
https://blog.stephencleary.com/2012/02/async-and-await.html
Will work like this, not that obvious though:
Func<Task> action = async () =>
{
await Task.Delay(3000);
};
Console.WriteLine("start");
await Task.Run(action);
Console.WriteLine("end");
You may try this:
Action action = async () =>
{
await Task.Delay(3000);
};
Console.WriteLine("start");
Task.Run(action).Wait();
Console.WriteLine("end");

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.

react different to multiple async calls

Imagine the following scenario :
public async Task DoMultipleWork() {
var uploadTask = UploadAsync(file);
var processingTask = Task.Run( () => DoCpuWork() );
await Task.WhenAll(uploadTask, processingTask);
Console.WriteLine("upload is done");
Console.WirteLine("processing is done");
}
How can I change that code so that it doesn't matter which one ends first, it execute some particular (sync or async) code.
So I fire the both task and when taskA or taskB ends, I just run some code (sync or async) independently of the other.
I think maybe ContinueWith but I'm not sure because it needs an another async method which is not really needed.
EDIT
As suggested by comments on answer, I want to clear that I want to execute different code depending on the task that completes, and get both Console.WriteLine executed as soon as the original task completes.
You want to use Task.WhenAny which returns the first task that completes. You can then tell which task completed by comparing to the original tasks. Before returning you should wait for the other one to complete explicitly (or wait for both with Task.WhenAll):
public async Task DoMultipleWork()
{
var uploadTask = UploadAsync(file);
var processingTask = Task.Run( () => DoCpuWork() );
var completedTask = await Task.WhenAny(uploadTask, processingTask);
Console.WriteLine("upload or processing is done");
if (completedTask == uploadTask)
{
// Upload completed
}
else
{
// Processing completed
}
await Task.WhenAll(uploadTask, processingTask) // Make sure both complete
Console.WriteLine("upload and processing are done");
}
As I describe on my blog, ContinueWith is dangerous unless you explicitly pass a scheduler. You should use await instead of ContinueWith in ~99% of cases (more detail in another blog post).
In your case:
private async Task UploadAsync(string filepath)
{
var result = await fileManager.UploadAsync(filepath);
Console.WriteLine($"Result from uploading file {result}");
}
private async Task ProcessAsync(string filepath, IProgress<T> progress)
{
await Task.Run(() => wordProcessor.Process(filepath, progress));
Console.WriteLine("processing completed");
}
...
await Task.WhenAll(UploadAsync(filepath), ProcessAsync(filepath, processingProgress));
public async Task DoMultipleWork() {
var uploadTask = UploadAsync(file);
var processingTask = Task.Run( () => DoCpuWork() );
uploadTask.ContinueWith((Task t) => Console.WriteLine("YOUR_MESSAGE"), TaskContinuationOptions.OnlyOnRanToCompletion);
processingTask.ContinueWith((Task t) => Console.WriteLine("YOUR_MESSAGE"), TaskContinuationOptions.OnlyOnRanToCompletion);
await Task.WhenAll(new []{uploadTask, processingTask});
}

async method in C# not asynchronous?

Having created the following console application I am a little puzzled why it seems to run synchronously instead of asynchronously:
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var total = CreateMultipleTasks();
stopwatch.Stop();
Console.WriteLine("Total jobs done: {0} ms", total.Result);
Console.WriteLine("Jobs done in: {0} ms", stopwatch.ElapsedMilliseconds);
}
static async Task<int> CreateMultipleTasks()
{
var task1 = WaitForMeAsync(5000);
var task2 = WaitForMeAsync(3000);
var task3 = WaitForMeAsync(4000);
var val1 = await task1;
var val2 = await task2;
var val3 = await task3;
return val1 + val2 + val3;
}
static Task<int> WaitForMeAsync(int ms)
{
Thread.Sleep(ms);
return Task.FromResult(ms);
}
}
When running the application, output is:
Total jobs done: 12000 ms
Jobs done in: 12003 ms
I would have expected somehing like:
Total jobs done: 12000 ms
Jobs done in: 5003 ms
Is this because when I use the Thread.Sleep method it stops further execution of the whole application? Or am I missing something important here?
Even when you convert to using Task.Run or Task.Delay as other answers suggest, you should avoid using the blocking Task.WaitAll anywhere inside async methods, as much as you can. Mixing asynchronous and synchronous code is usually a bad idea, it increases the number of redundantly blocked threads and promotes deadlocks.
Instead, use await Task.WhenAll and move the blocking wait to the top level (i.e., Main method in this case):
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var total = CreateMultipleTasks();
total.Wait();
stopwatch.Stop();
Console.WriteLine("Total jobs done: {0} ms", total.Result);
Console.WriteLine("Jobs done in: {0} ms", stopwatch.ElapsedMilliseconds);
}
static async Task<int> CreateMultipleTasks()
{
var task1 = Task.Run(() => WaitForMeAsync(5000));
var task2 = Task.Run(() => WaitForMeAsync(3000));
var task3 = Task.Run(() => WaitForMeAsync(4000));
await Task.WhenAll(new Task[] { task1, task2, task3 });
return task1.Result + task2.Result + task3.Result;
}
static int WaitForMeAsync(int ms)
{
// assume Thread.Sleep is a placeholder for a CPU-bound work item
Thread.Sleep(ms);
return ms;
}
}
On a side note, check Stephen Toub's "Should I expose asynchronous wrappers for synchronous methods?" and "Should I expose asynchronous wrappers for synchronous methods?"
You run the task in a synchounus manner. You can do something like this:
static async Task<int> CreateMultipleTasks()
{
var task1 = Task.Run<int>(() => WaitForMeAsync(5000));
var task2 = Task.Run<int>(() => WaitForMeAsync(3000));
var task3 = Task.Run<int>(() => WaitForMeAsync(4000));
Task.WaitAll(new Task[] { task1, task2, task3 });
return task1.Result + task2.Result + taks3.Result;
}
Using the three await in a row will NOT run the tasks in parallel. It will just free the thread while it is waiting (if you use await Task.Delay(ms) as Thread.Sleep(ms) is a blocking operation), but the current execution will NOT continue with task2 while task1 is "sleeping".
Your WaitForMeAsync method is just a simple sync method pretending to be an async one.
You don't perform anything async and Sleep() just blocks the thread.
Why would you want to delay async? (yes you can use await Task.Delay(ms)), need more input to help there.

Categories

Resources