如何从另一个类调用这种类型的扩展方法

Posted

技术标签:

【中文标题】如何从另一个类调用这种类型的扩展方法【英文标题】:How can I call this type of extension method from another class 【发布时间】:2020-09-03 04:28:33 【问题描述】:
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

我在 *** 找到了这个异步方法,并创建了这个方法的扩展方法:

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();
        
    

但是我不知道如何在另一个类中调用这种类型的扩展方法。

async方法中第一个参数CancellationToken和最后一个参数cancellationToken是什么?

我想创建以下异步方法 UpdatePlayerCountryData 的任务,并将其与扩展方法一起使用来确定 UpdatePlayerCountryData 是否在 5 秒内完成,如果没有则抛出新的 TimeoutException();。我收到一条错误消息,因为我不知道缺少的两个参数是什么:

错误 CS0839:缺少参数

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

如何将 UpdatePlayerCountryData 与扩展方法一起使用?如何从另一个类调用 CancelAfterAsync?

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

更新: 我改变了我的代码。方法 UpdatePlayerCountryData 会在 10 秒后使用 token.IsCancellationRequested 取消(TimeSpan.FromSeconds(10)),还是会在 await PlayFabClientAPI.UpdateUserDataAsync 花费超过 10 秒时取消?

是否将仅在等待 PlayFabClientAPI.UpdateUserDataAsync 完成后执行 (token.IsCancellationRequested),即使它需要几分钟?

if (token.IsCancellationRequested)

    return;

完整代码:

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

【问题讨论】:

你得到这个错误当你定义一个类的成员而不定义类时,命名空间不能直接包含字段或方法等成员。对于扩展方法,类应该是静态的,扩展方法的第一个方法应该在“this”关键字之前 请参考这个link,它将给出如何定义扩展方法。阅读文章后,请参考你提到的***链接写异步方法。 我将行更改为 public static async Task CancelAfterAsync(this Func> startTask, TimeSpan timeout, CancellationToken cancelToken)。但是如何从另一个类调用扩展方法呢?我该怎么做? 【参考方案1】:

请检查代码以了解使用情况。在您的帖子中发布的 cancelafter 功能也是一种方法。另一种做同样事情的方法如下。 也为了清楚地了解以这种方式取消任务及其后果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             
        
    


取消令牌的使用不正确。正确的用法应该是这样的

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


【讨论】:

我不明白取消在您的代码中是如何工作的。为什么在 var completedTask = await Task.WhenAny(timeoutTask, originalTask​​); 之后不使用 .Cancel()? 我已经用 cmets 和取消调用的用法更新了帖子。在继续之前,我想知道 UpdateUserDataAsync 是否有一个需要取消令牌的重载?如果不是,则扩展方法中根本不需要取消令牌,因为无论如何您的任务都无法取消。 如果代码抛出TimeoutException,你为什么要抓Exception 它可以用于日志记录。万一异常需要传播给调用者,然后在记录后重新抛出。如果不需要记录,请删除 try catch 子句。 不,我不认为 UpdateUserDataAsync 有一个需要取消令牌的重载。是否可以在我的情况下使用 TaskStatus 来确定任务是否成功完成(RanToCompletion)?如果 TaskStatus 为 Canceled 或 Faulted,我应该再次运行该任务吗? docs.microsoft.com/en-us/dotnet/api/…

以上是关于如何从另一个类调用这种类型的扩展方法的主要内容,如果未能解决你的问题,请参考以下文章

Java:从另一个类扩展侦听器方法的功能

C# 扩展方法

为啥我不能从扩展类型的基类调用扩展方法?

Prism - 如何测试 ShowDialogAsync(调用扩展方法而不是类方法)

ABPExtensions后缀扩展方法

你真的了解扩展方法吗?