0
async Task<TResult> CancelAfterAsync<TResult>(Func<CancellationToken, Task<TResult>> startTask, TimeSpan timeout, CancellationToken cancellationToken)
{
using (var timeoutCancellation = new CancellationTokenSource())
using (var combinedCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellation.Token))
{
    var originalTask = startTask(combinedCancellation.Token);
    var delayTask = Task.Delay(timeout, timeoutCancellation.Token);
    var completedTask = await Task.WhenAny(originalTask, delayTask);
    // Cancel timeout to stop either task:
    // - Either the original task completed, so we need to cancel the delay task.
    // - Or the timeout expired, so we need to cancel the original task.
    // Canceling will not affect a task, that is already completed.
    timeoutCancellation.Cancel();
    if (completedTask == originalTask)
    {
        // original task completed
        return await originalTask;
    }
    else
    {
        // timeout
        throw new TimeoutException();
    }
}
}

Asynchronously wait for Task<T> to complete with timeout

I found this async method here at stackoverflow and I created an extension method of this method:

public static async Task<TResult> CancelAfterAsync<TResult>(this Func<CancellationToken, Task<TResult>> startTask, TimeSpan timeout, CancellationToken cancellationToken)
{
    using (var timeoutCancellation = new CancellationTokenSource())
    using (var combinedCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellation.Token))
    {
        var originalTask = startTask(combinedCancellation.Token);
        var delayTask = Task.Delay(timeout, timeoutCancellation.Token);
        var completedTask = await Task.WhenAny(originalTask, delayTask);
        // Cancel timeout to stop either task:
        // - Either the original task completed, so we need to cancel the delay task.
        // - Or the timeout expired, so we need to cancel the original task.
        // Canceling will not affect a task, that is already completed.
        timeoutCancellation.Cancel();
        if (completedTask == originalTask)
        {
            // original task completed
            return await originalTask;
        }
        else
        {
            // timeout
            throw new TimeoutException();
        }
    }
}

But I don't know how to call this type of extension method in another class.

What is the first parameter CancellationToken and the last parameter cancellationToken in the async method?

I want to create a task of the following async method UpdatePlayerCountryData and use it with the extension method to find out if UpdatePlayerCountryData completes in 5 seconds and if not then throw new TimeoutException();. I get an error message because I don't know what the two missing arguments are:

Error CS0839: Argument missing

 var test = await Extensionmethods.CancelAfterAsync( , UpdatePlayerCountryData("Germany", "Berlin"), new TimeSpan(0, 0, 0, 5, 0), );

How can I use UpdatePlayerCountryData with the extension method? How can I call CancelAfterAsync from another class?

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");
     }
}

UPDATE: I changed my code. Will the method UpdatePlayerCountryData be canceled with token.IsCancellationRequested after 10 seconds(TimeSpan.FromSeconds(10)) or will cancelling not work when await PlayFabClientAPI.UpdateUserDataAsync takes longer than 10 seconds?

Will (token.IsCancellationRequested) only be executed after await PlayFabClientAPI.UpdateUserDataAsync finished, even if it would take minutes?

if (token.IsCancellationRequested)
{
    return;
}

Complete code:

public async Task PlayerAccountDetails()
{
    TimeSpan timeout = TimeSpan.FromSeconds(10); 
    CancellationTokenSource cts = new CancellationTokenSource();

    try
    {
        await UpdatePlayerCountryData("Country", "City", cts.Token).CancelAfter(timeout, cts.Token);
    }
    catch (Exception ex)
    {
        //catch exception here for logging
    }
}

public static async Task CancelAfter(this Task @task, TimeSpan timeout, CancellationToken token)
{
    var timeoutTask = Task.Delay(timeout, token);
    var originalTask = @task;
    var completedTask = await Task.WhenAny(timeoutTask, originalTask);

    if (completedTask == timeoutTask)
        throw new TimeoutException();

    await completedTask;
}

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

    if (token.IsCancellationRequested)
    {
        return;
    }

    if (resultprofile.Error != null)
        Console.WriteLine(resultprofile.Error.GenerateErrorReport());
    else
    {
        Console.WriteLine("Successfully updated user data");
    }
}
Tobey60
  • 147
  • 1
  • 5
  • 1
    You get this error A namespace cannot directly contain members such as fields or methods when u define members of a class without defining class. for extension method, class should be static and extension method's first method should be preceeded by "this" keyword – neelesh bodgal May 17 '20 at 12:16
  • Please refer this [link](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/how-to-implement-and-call-a-custom-extension-method) which will give how to define extension method. After reading the article, please refer the stackoverflow link u referred to write async method. – neelesh bodgal May 17 '20 at 12:22
  • I changed the line to public static async Task CancelAfterAsync(this Func> startTask, TimeSpan timeout, CancellationToken cancellationToken). But how can I call the extension method from another class? How can I do that? – Tobey60 May 17 '20 at 12:48

1 Answers1

0

Please check the code for usage. Also cancelafter function posted in your post is one way of doing it. Another way of same thing to doing it as follows. Also for clear understanding cancelling task in this fashion and its consequences here

 public static class Test
 {
    private  static 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");
        }
    }

    public static async Task CancelAfter(this Task @task, TimeSpan timeout,CancellationToken token)
    {
        var timeoutTask = Task.Delay(timeout, token);
        var originalTask = @task;
        var completedTask = await Task.WhenAny(timeoutTask, originalTask);

        if (completedTask == timeoutTask)
            throw new TimeoutException();

        await completedTask;
    }


    //Usage method
    public static async Task ExtensionMethodUsage()
    {
        //Timeout ,for demonstartion purpose i used 10 seconds
        //Modify according to your need
        TimeSpan timeout = TimeSpan.FromSeconds(10);

        //Cancellation Token 
        CancellationTokenSource cts = new CancellationTokenSource();

        //U can manually cancel the task here if a caller of the function
        //does not want to wait for timeout to occur.
        //Some mechanism to cancel the task.

        // If dont have a provision to cancel the task(which is not a good pattern)
        //There is no need to use cancellation token

        cts.Cancel();

        try
        {
           //Pass the cancellation token to method
           //When Cancel() is called all task will be cancalled.
            await UpdatePlayerCountryData("Country", "City",cts.Token).CancelAfter(timeout, cts.Token);
        }
        catch (Exception)
        {
            //catch exception here for logging             
        }
    }

}

Usage of cancellation token is incorrect. Correct usage would be something like this

 private static async Task UpdatePlayerCountryData(string country, string city, CancellationToken token)
 {
// UpdateUserDataAsync should take the Cancellation token,
// but I am afraid this method has any provision.             
var resultprofile = await PlayFabClientAPI.UpdateUserDataAsync(new PlayFab.ClientModels.UpdateUserDataRequest()
{
    Data = new Dictionary<string, string>() {
    {"Country", country},
    {"City", city}
    },
     Permission = PlayFab.ClientModels.UserDataPermission.Public
});

// Yes it would execute after the above await is complete
if (token.IsCancellationRequested)
{
    return;
}

if (resultprofile.Error != null)
    Console.WriteLine(resultprofile.Error.GenerateErrorReport());
else
{
    Console.WriteLine("Successfully updated user data");
}
}
neelesh bodgal
  • 632
  • 5
  • 14
  • I don't understand how cancellation is working in your code. Why are you not using .Cancel() after var completedTask = await Task.WhenAny(timeoutTask, originalTask);? – Tobey60 May 17 '20 at 16:56
  • I have updated the post with comments and usage of cancallation. Before moving ahead, i want to know does UpdateUserDataAsync has an overload that takes cancellationToken? If not then cancellation token is not required in extension method at all, since your task can not be cancelled in anyway. – neelesh bodgal May 18 '20 at 04:36
  • If the code is throwing `TimeoutException` why are you catching `Exception`? – Enigmativity May 18 '20 at 05:21
  • it can used for logging. Incase exception needs to be propogated up to caller then rethrow after logging. If logging is not required, remove try catch clause. – neelesh bodgal May 18 '20 at 05:43
  • No, I don't think that UpdateUserDataAsync has an overload that takes cancellationToken. Is it possible to use the TaskStatus in my case to find out if a task was completed successful(RanToCompletion)? Should I run the task again if the TaskStatus is Canceled or Faulted? https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus?view=netcore-3.1 – Tobey60 May 18 '20 at 08:46
  • yes u can check the status of the task to find out what happened. Also as you say UpdateUserDataAsync doesn't have cancellation support then cancellation support in the extension method is also not required. You can update the extension method by removing cancellation token when cancellation is not supported – neelesh bodgal May 18 '20 at 09:08
  • @neelesh bodgal Is it possible to cancel the task if I call UpdateUserDataAsync like that? CancellationTokenSource ctstask = new CancellationTokenSource(); try { playertasknew = await UpdatePlayerCountryData("Germany", "Berlin", ctstask).CancelTask(timeouttask, ctstask); } private static async Task UpdatePlayerCountryData(string country, string city, CancellationTokenSource cancelsource) How can I cancel the task if my UpdatePlayerCountryData uses CancellationTokenSource? – Tobey60 May 19 '20 at 09:37
  • I think that I can use CancellationTokenSource cancelsource in my method UpdatePlayerCountryData but I don't know how to cancel the method in CancelAfter. How can I do that? – Tobey60 May 19 '20 at 10:48
  • I think UpdatePlayerCountryData must be taken CancellationToken as input and not CancellationTokenSource. Please check it again. I have updated the code please check. Post your updated code too. – neelesh bodgal May 19 '20 at 12:45
  • I have added my code and new questions above. Will (token.IsCancellationRequested) only be executed after await PlayFabClientAPI.UpdateUserDataAsync finished, even if it would take minutes to finish await PlayFabClientAPI.UpdateUserDataAsync? I want to know if cancelling the method will always happen after 10 seconds or if it is not sure in my situation? – Tobey60 May 19 '20 at 13:28
  • I have update the code. To answer your last question after 10 sec, timeout exception would occur and task will faulted not cancelled. – neelesh bodgal May 19 '20 at 13:39
  • Thanx. I understand now that I can not cancel PlayFabClientAPI.UpdateUserDataAsync because it doesn't take a cancellation token. – Tobey60 May 19 '20 at 14:13