0

How can I cancel an asynchronous task when it takes very long to complete it or if it will probably never complete? Is it possible to use a given time(for example 10 seconds) for each task and when it doesn't complete in this given time, then the task will automatically be cancelled?

Is it possible to restart a task or create the same task again after it failed? What can I do if one of the tasks in a task list fails? Is it possible to only restart the failed task?

In my code, playerCountryDataUpdate should only be executed after each task in TasksList1 completed without error or exception. I want to restart a task when it fails. When the same task fails again, then don't restart it and display an error message on the screen. How can I do that?

bool AllMethods1Completed = false;
bool AllMethods2Completed = false;

public async Task PlayerAccountDetails()
{
    var playerCountryDataGet = GetPlayerCountryData();
    var playerTagsData = GetPlayerTagsData();
    var TasksList1 = new List<Task> { playerCountryDataGet, playerTagsData };

    try
    {
        await Task.WhenAll(TasksList1);
        AllMethods1Completed = true;
    }
    catch
    {
        AllMethods1Completed = false;
    }

    if (AllMethods1Completed == true)
    {
        var playerCountryDataUpdate = UpdatePlayerCountryData("Germany", "Berlin");
        var TasksList2 = new List<Task> { playerCountryDataUpdate };

        try
        {
            await Task.WhenAll(TasksList2);
            AllMethods2Completed = true;
        }
        catch
        {
            AllMethods2Completed = false;
        }
    }
}

private async Task GetPlayerTagsData()
{
    var resultprofile = await PlayFabServerAPI.GetPlayerTagsAsync(new PlayFab.ServerModels.GetPlayerTagsRequest()
    {
        PlayFabId = PlayerPlayFabID
    });

    if (resultprofile.Error != null)
        Console.WriteLine(resultprofile.Error.GenerateErrorReport());
    else
    {
        if ((resultprofile.Result != null) && (resultprofile.Result.Tags.Count() > 0))
            PlayerTag = resultprofile.Result.Tags[0].ToString();
    }
}

private async Task GetPlayerCountryData()
{
    var resultprofile = await PlayFabClientAPI.GetUserDataAsync(new PlayFab.ClientModels.GetUserDataRequest()
    {
        PlayFabId = PlayerPlayFabID,
        Keys = null
    });

    if (resultprofile.Error != null)
        Console.WriteLine(resultprofile.Error.GenerateErrorReport());
    else
    {
        if (resultprofile.Result.Data == null || !resultprofile.Result.Data.ContainsKey("Country") || !resultprofile.Result.Data.ContainsKey("City"))
            Console.WriteLine("No Country/City");
        else
        {
            PlayerCountry = resultprofile.Result.Data["Country"].Value;
            PlayerCity = resultprofile.Result.Data["City"].Value;
        }
    }
}

private async Task UpdatePlayerCountryData(string country, string city)
{
    var resultprofile = await PlayFabClientAPI.UpdateUserDataAsync(new PlayFab.ClientModels.UpdateUserDataRequest()
    {
        Data = new Dictionary<string, string>() {
            {"Country", country},
            {"City", city}
            },
        Permission = PlayFab.ClientModels.UserDataPermission.Public
    });

    if (resultprofile.Error != null)
        Console.WriteLine(resultprofile.Error.GenerateErrorReport());
    else
        Console.WriteLine("Successfully updated user data");
}
Tobey60
  • 147
  • 1
  • 5

2 Answers2

2

You need to build a cancellation mechanism directly into the task itself. C# provides a CancellationTokenSource and CancellationToken classes to assist with this. https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=netcore-3.1

Add an (optional) CancellationToken to your task's parameters. Then check the token at appropriate intervals to determine if the task needs to abort before it completes.

In the case of a long running query, it would be best to figure out how to break the query into chunks and then check the CancellationToken between queries.

private async Task GetPlayerXXXData(CancellationToken ct = null) {
   int limit = 100;
   int total = Server.GetPlayerXXXCount();
   List<PlayerXXXData> results = new List<PlayerXXXData>();
   while((ct == null || ct.IsCancellationRequested) && result.Count < total) {
      result.AddRange(Server.GetPlayerXXXData(result.Count, limit));
   }
   return results;
}

Mind the above has no error handling in it; but you get the idea. You might consider making it faster (to start using the data) by implementing Deferred Execution with your own custom IEnumerable implementation. Then you can query one chunk and iterate over that chunk before querying for the next chunk. This could also help prevent you from loading too much into RAM - depending upon the number of records you are intending to process.

1

set timeout in your logic to suspend the task:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

Asynchronously wait for Task<T> to complete with timeout

and also put try catch blocks in a while loop with a flag until you want to retry

    var retry=0;

    while (retry<=3)
{
     try{
          await with timeout
          raise timeout exception
        }
     catch(catch timeout exception here )
       {
          retry++;
          if(retry ==3)
          {
             throw the catched exception  here
          }
       }
}
Rupinder Kaur
  • 82
  • 1
  • 13
  • I don't understand how to use var task = SomeOperationAsync(); in your while loop. Is it necessary to create 3 different while loops? One loop for each of my 3 different tasks? Or is it possible to create only 2 while loops? One while loop for each task list(TasksList1 and TasksList2). Is it possible to catch all types of exceptions in your try catch block or will it only catch the timeout exception? Will the task automatically be cancelled when it comes to the timeout exception? Can you please show in detail how to use your code with my example? – Tobey60 May 15 '20 at 12:12
  • your whenAll is an aggragate of all the tasks in list, and is considered as one task, so if any of the tasks in list fails, then when all fails, you cannot retrieve list of failed tasks , for that you have to run them independently without using when all – Rupinder Kaur May 15 '20 at 13:36
  • see this , it should be helpful for your case, not ideal but still may resolve your problem : https://stackoverflow.com/questions/43283460/error-handling-for-tasks-inside-task-whenall – Rupinder Kaur May 15 '20 at 13:43
  • Should I use Task.WhenAny in the try block or before the try block? var retry=0; while (retry<=3) {try{ if (await Task.WhenAny(task, Task.Delay(timeout)) == task) { // task completed within timeout} – Tobey60 May 15 '20 at 13:53