I am trying to get my head around the async-await however I wrote a small test and cannot understand the results i am getting.
Consider the following code (WPF Application, inside MainWindow):
private async void Button_Click(object sender, RoutedEventArgs e)
{
Stopwatch l_Stopwatch = new Stopwatch();
Debug.WriteLine("Entering Button_Click...");
l_Stopwatch.Start();
await DoSomethingAsync();
l_Stopwatch.Stop();
Debug.WriteLine("DoSomethingAsync completed in {0}", l_Stopwatch.ElapsedMilliseconds);
l_Stopwatch.Restart();
await DoSomethingElseAsync();
l_Stopwatch.Stop();
Debug.WriteLine("DoSomethingElseAsync completed in {0}", l_Stopwatch.ElapsedMilliseconds);
}
private async Task DoSomethingAsync()
{
Debug.WriteLine("Doing something Async");
await Task.Delay(5000);
}
private async Task DoSomethingElseAsync()
{
await Task.Run((Action)(async () => {
Debug.WriteLine("Doing something else Async");
await Task.Delay(5000);
}));
}
i would expect DoSomethingElseAsync to wait ~5000ms before continuing, however I keep getting the following
Entering Button_Click...
Doing something Async
DoSomethingAsync completed in 5005
Doing something else Async
DoSomethingElseAsync completed in 3
So at this point I think there is something fundamental that I am not understanding of the async-await way...
Can anyone shed light?
The problem is here:
await Task.Run((Action)(async () => {
Debug.WriteLine("Doing something else Async");
await Task.Delay(5000);
}));
You cast to Action delegate which doesn't return anything and cannot be awaited (there is no Task to await because Action returns void).
In your case you can simply change code to:
await Task.Run(async () => {
Debug.WriteLine("Doing something else Async");
await Task.Delay(5000);
});
OR
you can cast to Func<Task> delegate which returns Task that can be awaited:
await Task.Run((Func<Task>)(async () => {
Debug.WriteLine("Doing something else Async");
await Task.Delay(5000);
}));
Additional info:
If you see compiler generated code (SharpLab) you will get differences:
When you cast to Action compiler creates AsyncVoidMethodBuilder
When you cast to Func<Task> or do not cast at all compiler creates
AsyncTaskMethodBuilder
Related
I know I am asking an exist question. I read many articles about this but I still confused. Maybe my English skill is not good enough.
Here is my code at first:
void dovoid1(){
//dosomething
}
void dovoid2(){
//dosomething
}
void dovoid3(){
//dosomething
}
and
void action()
{
dovoid1();
Thread.Sleep(1000);
dovoid2();
Thread.Sleep(1000);
dovoid3();
Thread.Sleep(1000);
action();
}
As you see, the void action() will do some task, and have a sleep between them. Afer that, it repeat itself.
Now I want to avoid Thread.Sleep() because it blocks the UI. So I try to use async/await.
private async void action()
{
dovoid1();
Task.Delay(1000);
dovoid2();
Task.Delay(1000);
dovoid3();
Task.Delay(1000);
action();
}
But it give me errors. I don't know where and When I should use async or await. Thank you!
You need to await the delays to create the desired time gap between calls. Await in this context yields control until Task.Delay completes.
Also, if action() is not an event handler then it should probably be async Task instead of async void (see why is void async bad?).
private async Task action()
{
dovoid1();
await Task.Delay(1000);
dovoid2();
await Task.Delay(1000);
dovoid3();
await Task.Delay(1000);
action();
}
you can use this approach in your parent function for non-blocking your UI
await Task.Run(() => {
// Do lot of work here
});
in your case
await Task.Run(() => action());
if your action method is async
async Task action(){}
then
await Task.Run(async () => await action());
you can simply convert your action method to async by putting await keyword on every Task. Since await keyword can only be used with async method, You need to convert your method to async.
async Task action()
{
dovoid1();
await Task.Delay(1000);
dovoid2();
await Task.Delay(1000);
dovoid3();
await Task.Delay(1000);
action();
}
please keep in mind that if your dovoid1, dovoid2, dovoid3 includes Task then they are also needed to be converted to async method and needed to be awaited.
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");
I am writing a windows store app and needs some help on Task.Run method. I am calling service to retrieve data from a service; because I want to cancel the task when internet is disconnected I am passing CancellationToken
await Task.Run(async () => await Download1(), cts.Token);
There is another download method which should run after this above task is finished so I am using await. Both the tasks write to same files so I want to make sure that they do not run in parallel.
await Task.Run(async () => await Download2(), cts.Token);
The issue is that above task 2 starts without task 1 is finished and so both task run in parallel. What I am doing wrong? Please advise.
Download1 looks like this:-
public async Task Download1()
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
Status = "Downloading!";
var ListSetupTasks = new List<SetupView>();
foreach (var setup in AllSetupTasks.Setup)
{
ListSetupTasks.Add(new SetupViewModel(setup));
}
IEnumerable<Task> downloadTasksQuery = from setup in ListSetupTasks where setup.TaskType == TaskType.Web select _masterlistrepository.GetTask(setup, false, datetime, branch);
Task[] downloadTasks = downloadTasksQuery.ToArray();
await Task.WhenAll(downloadTasks);
IEnumerable<Task> FinalTasksQuery = from setup in ListSetupTasks where setup.TaskType == TaskType.Web select _masterlistrepository.GetTask(setup, false);
foreach (var task in FinalTasksQuery)
{
await task;
}
});
}
The CancellationToken is used like this:-
async void NetworkChanged(object sender, NetworkConnectionStatusChangedEventArgs e)
{
if (e.Value == false && LoadingData == true)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
await _alertmessageservice.ShowAsync("", "Network error. Please retry!");
cts.Cancel();
});
}
}
You use the CoreDispatcher.RunAsync which accepts a DispatchedHandler. DispatchedHandler has this signature:
public delegate void DispatchedHandler()
So when you pass in an async lambda it will end up being async void. That makes it impossible to await as there's no task being returned. This means that everything after an await in your lambdas will run on a different thread concurrently.
You shouldn't be passing an async delegate to that method.
As a side note, passing a CancellationToken doesn't enable to automatically stop the operation. It can only stop the operation before it starts or if you're observing the token somewhere inside the operation.
So, to actually use the CancellationToken Download should accept use it which makes the Task.Run redundant:
await Download1Async(cts.Token);
Can you just call them sequentially?
await Task.Run(async () =>
{
await Download1();
await Download2();
});
You might not even need Task.Run, unless you are calling from a UI thread. If you're already on a background thread, then try just:
await Download1();
await Download2();
Of course, your cancellation token should probably be passed into the download function either way.
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});
}
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);