简体   繁体   中英

How can I call this type of extension method from another class

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM