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:
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:
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: