I've got the following code which seems to run fine apart from the continuation on the WhenAll ... await Task.WhenAll(syncTasks).ContinueWith ... is run before all four methods are completed. Would appreciate any guidance on what I'm doing wrong here. I don't really feel like I understand how to arrange complex async functionality and what seems to be happening supports that. This is in a Xamarin App BTW although I don't suppose that really matters.
private async Task SyncItems()
{
var updateItemOnes = Task.Run(() =>
{
UpdateItemOnesToServer(itemOnesToUpdate).ContinueWith(async (result) => {
if (!result.IsFaulted && !result.IsCanceled)
{
await UpdateItemOnesToLocal(itemOnesToUpdate);
}
});
});
syncTasks.Add(updateItemOnes);
var updateItemTwos = Task.Run(() =>
{
UpdateItemTwosToServer(itemTwosToUpdate).ContinueWith(async (result) => {
if (!result.IsFaulted && !result.IsCanceled)
{
await UpdateItemTwosToLocal(itemTwosToUpdate);
}
});
});
syncTasks.Add(updateItemTwos );
//Show Loading Dialog
await Task.WhenAll(syncTasks).ContinueWith((result) => {
if (!result.IsFaulted && !result.IsCanceled)
{
//Success
}
else
{
//Error
}
//Hide Loading Dialog
});
}
private async Task UpdateItemOnesToServer(IEnumerable<Item> itemOnesToUpdate)
{
try
{
var listofTasks = new List<Task>();
foreach (var item in itemOnesToUpdate)
{
var convertItemOneTask = Task.Run(async () => {
//Convert Image File in Item to Base64 here
});
listofTasks.Add(convertItemOneTask);
}
await Task.WhenAll(listofTasks);
var response = await _apiManager.SaveItemOnes(itemOnesToUpdate);
if (response.IsSuccessStatusCode)
{
//Update ItemOnes for Local Update with Response Values
}
}
catch
{
throw;
}
}
private async Task UpdateItemOnesToLocal(IEnumerable<Item> itemOnesToUpdate)
{
var listOfTasks = new List<Task<bool>>();
foreach (var itemOne in itemOnesToUpdate)
{
listOfTasks.Add(_localService.UpdateItemOne(itemOne));
}
await Task.WhenAll<bool>(listOfTasks);
}
private async Task UpdateItemTwosToServer(IEnumerable<ItemOne> itemTwosToUpdate)
{
try
{
var listofTasks = new List<Task>();
foreach (var item in itemTwosToUpdate)
{
var convertItemTwoTask = Task.Run(async () => {
//Convert Image File in Item to Base64 here
});
listofTasks.Add(convertItemTwoTask);
}
await Task.WhenAll(listofTasks);
var response = await _apiManager.SaveItemTwos(itemTwosToUpdate);
if (response.IsSuccessStatusCode)
{
//Update ItemTwos for Local Update with Response Values
}
}
catch
{
throw;
}
}
private async Task UpdateItemTwosToLocal(IEnumerable<ItemTwo> itemTwosToUpdate)
{
var listOfTasks = new List<Task<bool>>();
foreach (var itemTwo in itemTwosToUpdate)
{
listOfTasks.Add(_localService.UpdateItemTwo(itemTwo));
}
await Task.WhenAll<bool>(listOfTasks);
}
Thanks in advance to anyone who can provide a little clarity. It will be much appreciated.
So there are a few problems with this code.
someTask.ContinueWith(X)
Basically this says "once the someTask is completed, do X" (there's more going on but in this case think of it like this). However if you await someTask this will not include the ContinueWith part. So like this the Task.WhenAll(syncTasks) will not wait on your ContinueWith parts.
var updateItemOnes = Task.Run(() => UpdateItemOnesToServer()) wrappers. There is no awaiting here, so this will create a Task that just starts the UpdateItemOnesToServer task. That is done instantly.
If you would like to see what is happening in practice use this test class:
class TestAsyncClass
{
public async Task Run()
{
var tasks = new List<Task>();
Console.WriteLine("starting tasks");
var task1 = Task.Run(() => {
FakeServerCall1().ContinueWith(async (result) =>
{
if (!result.IsFaulted && !result.IsCanceled)
await FakeLocalCall1();
});
});
tasks.Add(task1);
var task2 = Task.Run(() => {
FakeServerCall2().ContinueWith(async (result) =>
{
if (result.IsCompletedSuccessfully)
await FakeLocalCall2();
});
});
tasks.Add(task2);
Console.WriteLine("starting tasks completed");
await Task.WhenAll(tasks);
Console.WriteLine("tasks completed");
}
public async Task<bool> FakeServerCall1()
{
Console.WriteLine("Server1 started");
await Task.Delay(3000);
Console.WriteLine("Server1 completed");
return true;
}
public async Task<bool> FakeServerCall2()
{
Console.WriteLine("Server2 started");
await Task.Delay(2000);
Console.WriteLine("Server2 completed");
return true;
}
public async Task<bool> FakeLocalCall1()
{
Console.WriteLine("Local1 started");
await Task.Delay(1500);
Console.WriteLine("Local1 completed");
return true;
}
public async Task<bool> FakeLocalCall2()
{
Console.WriteLine("Local2 started");
await Task.Delay(2000);
Console.WriteLine("Local2 completed");
return true;
}
}
You'll see that the output is as follows:
starting tasks
starting tasks completed
Server1 started
Server2 started
tasks completed
Server2 completed
Local2 started
Server1 completed
Local1 started
Local2 completed
Local1 completed
Notice here the "tasks completed" is called straight after starting the two tasks.
Now if we change the Run method like this I think we'll get the functionality you're looking for:
public async Task Run()
{
var tasks = new List<Task>();
Console.WriteLine("starting tasks");
var task1 = Task.Run(async () =>
{
await FakeServerCall1();
await FakeLocalCall1();
});
tasks.Add(task1);
var task2 = Task.Run(async() =>
{
await FakeServerCall2();
await FakeLocalCall2();
});
tasks.Add(task2);
Console.WriteLine("starting tasks completed");
await Task.WhenAll(tasks);
Console.WriteLine("tasks completed");
}
Which will output:
starting tasks
starting tasks completed
Server1 started
Server2 started
Server2 completed
Local2 started
Server1 completed
Local1 started
Local2 completed
Local1 completed
tasks completed
So we see here that Local1 is always after Server1 and Local2 is always after Server2 and "tasks completed" is always after Local1 & Local2
Hope this helps!
Edit:
From you comment you said you would like to see any exceptions that occurred in the process. This is where you could use ContinueWith (it is also fired when exceptions are throw:
await Task.WhenAll(tasks).ContinueWith((result) =>
{
if (result.IsFaulted)
{
foreach (var e in result.Exception.InnerExceptions)
{
Console.WriteLine(e);
}
}
});
If you change the following test calls:
public async Task<bool> FakeServerCall2()
{
Console.WriteLine("Server2 started");
await Task.Delay(1000);
Console.WriteLine("Crashing Server2");
throw new Exception("Oops server 2 crashed");
}
public async Task<bool> FakeLocalCall1()
{
Console.WriteLine("Local1 started");
await Task.Delay(1500);
Console.WriteLine("crashing local1");
throw new Exception("Oh ohh, local1 crashed");
}
This will be your output:
starting tasks
starting tasks completed
Server1 started
Server2 started
Crashing Server2
Server1 completed
Local1 started
crashing local1
System.Exception: Oh ohh, local1 crashed
at TestConsoleApp.TestAsyncClass.FakeLocalCall1() in ~\TestConsoleApp\TestConsoleApp\Program.cs:line 67
at TestConsoleApp.TestAsyncClass.b__0_0() in ~\TestConsoleApp\TestConsoleApp\Program.cs:line 17
System.Exception: Oops server 2 crashed
at TestConsoleApp.TestAsyncClass.FakeServerCall2() in ~\TestConsoleApp\TestConsoleApp\Program.cs:line 59
at TestConsoleApp.TestAsyncClass.b__0_1() in ~\TestConsoleApp\TestConsoleApp\Program.cs:line 23
tasks completed
Related
I am testing web socket subscriptions in my tests and I would like to wait for response from callback and then end the test, if no response is received after timeout end the test.
This is what I have now (simplified) but I am not sure if its the way how to do it.
public async Task WaitForPing()
{
var cancellationTokenSource = new CancellationTokenSource(5_000);
var pinged = false;
using var _ = Client.OnPing(_ =>
{
pinged = true;
cancellationTokenSource.Cancel();
}
await Client.Run();
await Task
.Delay(-1, cancellationTokenSource.Token)
.ContinueWith(_ => { }, CancellationToken.None);
Assert(pinged);
}
A proper method should be like that:
static Task<string> WaitForResponseAsync(CancellationTokenSource cancellationTokenSource = default)
{
var tcs = new TaskCompletionSource<string>();
// Register a method that throws an exception when task cancelled.
cancellationTokenSource.Token.Register(()=> throw new Exception("Timed out!"));
// Replace this task with your async operation. Like OnPing(_ => ...
Task.Run(async () =>
{
await Task.Delay(30_000); // Response will be received after 30 seconds
tcs.SetResult("Hello World");
});
return tcs.Task; // Return awaitable task
}
And place where you call that method:
try
{
Console.WriteLine(await WaitForResponseAsync(new CancellationTokenSource(5_000))); // Time out is 5 seconds
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Don't worry about the task in a task, you should replace it with your event. So if I customize it according to your method, it should be something like that:
Task WaitForPing(CancellationTokenSource cancellationTokenSource)
{
var tcs = new TaskCompletionSource();
cancellationTokenSource.Token.Register(() => throw new Exception("Timed out"));
// Or just call SetResult to finish the task before completed without exception:
// cancellationTokenSource.Token.Register(() => tcs.SetResult());
// (Personally I do not recommend this one)
using var _ = Client.OnPing(_ =>
{
tcs.SetResult();
};
return tcs.Task;
}
I have a collection of tasks that have to finish before one last task is run. Specifically:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var task1 = Task.Run(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Task1");
});
var task2 = Task.Run(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Task2");
});
var task3 = Task.Run(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Task2");
});
var last = Task.Run(() =>
{
Console.WriteLine("Last");
});
var tasks = new List<Task>();
tasks.Add(task1);
tasks.Add(task2);
tasks.Add(task3);
await Task.WhenAll(tasks).ContinueWith(t => last);
}
Currently they finish like this:
Last
Task2
Task3
Last1
I want them to finish like this:
Task1
Task2
Task3
Last
The order of first 3 does not matter, what matters is that Last task finishes last.
I cannot block the thread or wait for the collection to finish and similar stuff, only the Last task has to perform after first three are done.
Tasks are created "hot", i.e., already in-progress. If you want to delay the starting of a task, then use a delegate (Func<Task>) or a separate async method.
I like local async methods for this kind of behavior:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
async Task Task1() => Task.Run(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Task1");
});
async Task Task2() => Task.Run(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Task2");
});
async Task Task3() => Task.Run(() =>
{
Thread.Sleep(5000);
Console.WriteLine("Task2");
});
async Task Last() => Task.Run(() =>
{
Console.WriteLine("Last");
});
var tasks = new List<Task>();
tasks.Add(Task1());
tasks.Add(Task2());
tasks.Add(Task3());
await Task.WhenAll(tasks);
await Last();
}
P.S. Don't use ContinueWith.
//var last = Task.Run(() =>
//{
// Console.WriteLine("Last");
//});
var tasks = new List<Task>();
tasks.Add(task1);
tasks.Add(task2);
tasks.Add(task3);
await Task.WhenAll(tasks); //.ContinueWith(t => last);
Task.Run(() =>
{
Console.WriteLine("Last");
});
I have a method RestartAsync which starts a method DoSomethingAsync. When RestartAsync is called again it should cancel DoSomethingAsyncand await until it is finished (DoSomethingAsync can NOT be cancelled synchronously and it should NOT be called when a previous task is still in progress).
My first approach looked like this:
public async Task RestartTest()
{
Task[] allTasks = { RestartAsync(), RestartAsync(), RestartAsync() } ;
await Task.WhenAll(allTasks);
}
private async Task RestartAsync()
{
_cts.Cancel();
_cts = new CancellationTokenSource();
await _somethingIsRunningTask;
_somethingIsRunningTask = DoSomethingAsync(_cts.Token);
await _somethingIsRunningTask;
}
private static int _numberOfStarts;
private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
_numberOfStarts++;
int numberOfStarts = _numberOfStarts;
try
{
Console.WriteLine(numberOfStarts + " Start to do something...");
await Task.Delay(TimeSpan.FromSeconds(1)); // This operation can not be cancelled.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.WriteLine(numberOfStarts + " Finished to do something...");
}
catch (OperationCanceledException)
{
Console.WriteLine(numberOfStarts + " Cancelled to do something...");
}
}
The actual output when calling RestartAsync three times looks like this (Note that the second run is cancelling and awaiting the first, but at the same time the third run is also awaiting the first instead of cancelling and awaiting the second one):
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
3 Start to do something...
2 Finished to do something...
3 Finished to do something...
But what I want to achieve is this output:
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
2 Cancelled to do something...
3 Start to do something...
3 Finished to do something...
My current solution is the following:
private async Task RestartAsync()
{
if (_isRestarting)
{
return;
}
_cts.Cancel();
_cts = new CancellationTokenSource();
_isRestarting = true;
await _somethingIsRunningTask;
_isRestarting = false;
_somethingIsRunningTask = DoSomethingAsync(_cts.Token);
await _somethingIsRunningTask;
}
Then I get this output:
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
2 Finished to do something...
Now at least DoSomethingAsync is not started while it is still in progress (Note that third run is ignored, which does not really matter, because it should cancel the second run otherwise).
But this solution doesn't feel good and I have to repeat this ugly pattern wherever I want this kind of behavior. Is there any good pattern or framework for this kind of restart mechanic?
I think the problem is inside RestartAsync method. Beware that an async method will immediately return a task if it's going to await something, so second RestartAsync actually return before it swap its task then third RestartAsync comes in and awaiting the task first RestartAsync.
Also if RestartAsync is going to be executed by multiple thread, you may want to wrap _cts and _somethingIsRunningTask into one and swap values with Interlocked.Exchange method to prevent race condition.
Here is my example code, not fully tested:
public class Program
{
static async Task Main(string[] args)
{
RestartTaskDemo restartTaskDemo = new RestartTaskDemo();
Task[] tasks = { restartTaskDemo.RestartAsync( 1000 ), restartTaskDemo.RestartAsync( 1000 ), restartTaskDemo.RestartAsync( 1000 ) };
await Task.WhenAll( tasks );
Console.ReadLine();
}
}
public class RestartTaskDemo
{
private int Counter = 0;
private TaskEntry PreviousTask = new TaskEntry( Task.CompletedTask, new CancellationTokenSource() );
public async Task RestartAsync( int delay )
{
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskEntry previousTaskEntry = Interlocked.Exchange( ref PreviousTask, new TaskEntry( taskCompletionSource.Task, cancellationTokenSource ) );
previousTaskEntry.CancellationTokenSource.Cancel();
await previousTaskEntry.Task.ContinueWith( Continue );
async Task Continue( Task previousTask )
{
try
{
await DoworkAsync( delay, cancellationTokenSource.Token );
taskCompletionSource.TrySetResult( true );
}
catch( TaskCanceledException )
{
taskCompletionSource.TrySetCanceled();
}
}
}
private async Task DoworkAsync( int delay, CancellationToken cancellationToken )
{
int count = Interlocked.Increment( ref Counter );
Console.WriteLine( $"Task {count} started." );
try
{
await Task.Delay( delay, cancellationToken );
Console.WriteLine( $"Task {count} finished." );
}
catch( TaskCanceledException )
{
Console.WriteLine( $"Task {count} cancelled." );
throw;
}
}
private class TaskEntry
{
public Task Task { get; }
public CancellationTokenSource CancellationTokenSource { get; }
public TaskEntry( Task task, CancellationTokenSource cancellationTokenSource )
{
Task = task;
CancellationTokenSource = cancellationTokenSource;
}
}
}
This is a concurrency problem. So, you'll need a solution for concurrency problems: a semaphore.
In the generic case, you should account also for when the method being runs throws an OperationCanceledException:
private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
_numberOfStarts++;
int numberOfStarts = _numberOfStarts;
try
{
Console.WriteLine(numberOfStarts + " Start to do something...");
await Task.Delay(TimeSpan.FromSeconds(1)); // This operation can not be cancelled.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.WriteLine(numberOfStarts + " Finished to do something...");
}
catch (OperationCanceledException)
{
Console.WriteLine(numberOfStarts + " Cancelled to do something...");
throw;
}
}
Try this:
private SemaphoreSlim semaphore = new SemaphoreSlim(1);
private (CancellationTokenSource cts, Task task)? state;
private async Task RestartAsync()
{
Task task = null;
await this.semaphore.WaitAsync();
try
{
if (this.state.HasValue)
{
this.state.Value.cts.Cancel();
this.state.Value.cts.Dispose();
try
{
await this.state.Value.task;
}
catch (OperationCanceledException)
{
}
this.state = null;
}
var cts = new CancellationTokenSource();
task = DoSomethingAsync(cts.Token);
this.state = (cts, task);
}
finally
{
this.semaphore.Release();
}
try
{
await task;
}
catch (OperationCanceledException)
{
}
}
I have the following pieces of code:
private async void buttonStart_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
{
Bot b = new Bot(_names);
var result = await b.Start(false, true, false);
MessageBox.Show("Done");
}
public async Task<bool> Start(bool instagram, bool twitter, bool xbox)
{
if (twitter)
{
Twitter t = new Twitter(Names);
await t.CheckNames();
}
return true;
}
public Task CheckNames()
{
List<Task> tasks = new List<Task>();
foreach (Name name in Names)
{
tasks.Add(Task.Factory.StartNew(async () =>
{
TwitterResponse result = await Check(name);
MessageBox.Show(result.msg);
}));
}
return Task.WhenAll(tasks);
}
public async Task<TwitterResponse> Check(Name name)
{
HttpClient http = new HttpClient();
HttpResponseMessage response = await http.GetAsync(string.Format("https://twitter.com/users/username_available?username={0}", name.Value));
string html = response.Content.ReadAsStringAsync().Result;
TwitterResponse result = new JavaScriptSerializer().Deserialize<TwitterResponse>(html);
return result;
}
However, I always seem to get the MessageBox saying "Done" before any of the tasks are completed.
Am I doing something wrong, how can I make sure all of the tasks actually complete before getting the messagebox?
The problem is the line tasks.Add(Task.Factory.StartNew(async () =>, you should almost never be using Task.Factory.StartNew and instead use Task.Run(.
The object that StartNew is returning is a Task<Task> which means it does not wait for the inner task to finish. You must either call .Unwrap() on the output of the StartNew before you add it to the collection or, much much better use Task.Run(
tasks.Add(Task.Run(async () =>
{
TwitterResponse result = await Check(name);
MessageBox.Show(result.msg);
}));
Which has a overload that takes in a Func<Task> and will unwrap the inner task for you.
I have created 3 Tasks. Task3 depend on the result from Task1 and Task2.
While debugging the code it executes correctly but while running the application Task3 gets execute before Task1 and Task2 complete.
Sample code :
enter code here
public void LoadData()
{
// Set up tasks
Task Task1 = GetTask1Data();
Task Task2 = GetTask2Data();
Task Task3 = GetTask3Data();
new TaskFactory(TaskScheduler).StartNew(() =>
{
// Start first
Task1.Start();
new TaskFactory(TaskScheduler).ContinueWhenAll(
new[] {Task1 },
completedTasks =>
{
Task2.Start();
Task2.ContinueWith(r => Task3.Start());
});
}
}
}
private Task GetTask3Data()
{
return new Task(() =>
{
Task<ICollection<Object>> task = SubTask1();
if (task == null)
{
return;
}
task.ContinueWith(result =>
{
if (result != null)
Debug.WriteLine("Got correct data");
});
});
}
private Task<ICollection<Object>> SubTask1()
{
return new TaskFactory(TaskScheduler).StartNew(() =>
{
if (<bool condition return by Task1> && <book condition return by Task2>)
{
//Return executed data
}
return null;
});
}
Thanks in advance.
It looks like you might be using some kind of global state to hold the results of Task1 and Task2. Try to compose your tasks into a flow.
var task1Result = await GetTask1();
var task2Result = await GetTask2();
var task3Result = await GetTask3(task1Result, task2Result);