如何将具有ref参数的函数转换为异步函数

本文关键字:函数 转换 异步 参数 ref | 更新日期: 2023-09-27 18:28:19

我有以下函数,我想将其转换为异步/非锁定函数。

以下是当前形式的函数:

   private static void BlockForResponse(ref bool localFlag)
    {
        int count = 0;
        while (!localFlag)
        {
            Thread.Sleep(200);
            if (count++ > 50)   // 200 * 50 = 10 seconds
            {
                //timeout
                throw new TimeOutException();
            }
        }
    }

这是我的尝试:

  private static async Task BlockForResponse(ref bool localFlag)
    {
        int count = 0;
        while (!localFlag)
        {
            await Task.Delay(200);
            if (count++ > 50)   // 200 * 50 = 10 seconds
            {
                //timeout
                throw new TimeOutException();
            }
        }
    }

然而,我得到了一个编译错误,说异步函数不能有ref或out参数。然而,这是该功能的核心功能。

是否可以将其转换为异步函数?

代码说明:

我必须承认这是一段奇怪的代码,让我试着解释一下它试图做什么:

所以有一个第三方dll,我需要使用。它为我提供服务,我很遗憾无法控制这个dll。

它的工作方式,我调用dll中的一个命令,为它提供一个回调函数,它在完成任务后调用该函数。

只有当我得到那个电话的结果后,我才能继续做我想做的事情。因此需要这个功能。

我调用dll,为其提供回调函数:

    private bool _commandFlag = false;
    private bool _commandResponse;
    public async Task ExecuteCommand(string userId, string deviceId)
    {
        var link = await LinkProviderAsync.GetDeviceLinkAsync(deviceId, userId);
        try
        {               
            //execute command
            if (link.Command(Commands.ConnectToDevice, CallBackFunction))
            {
                BlockForResponse(ref _commandFlag);
                return;     //Received a response
            }
            else
            {   //Timeout Error                    
                throw new ConnectionErrorException();
            }
        }
        catch (Exception e)
        {
            throw e;
        }
    }
    private void CallBackFunction(bool result)
    {
        _commandResponse = result;
        _commandFlag = true;
    }

如何将具有ref参数的函数转换为异步函数

按照它的工作方式,我调用dll中的一个命令,为它提供一个回调函数,它在完成任务后调用该函数。

然后,您真正想要的是使用TaskCompletionSource<T>创建一个TAP方法,类似于此方法。

public static Task<bool> CommandAsync(this Link link, Commands command)
{
  var tcs = new TaskCompletionSource<bool>();
  if (!link.Command(command, result => tcs.TrySetResult(result)))
      tcs.TrySetException(new ConnectionErrorException());
  return tcs.Task;
}

有了这个扩展方法,您的调用代码就干净多了:

public async Task ExecuteCommand(string userId, string deviceId)
{
  var link = await LinkProviderAsync.GetDeviceLinkAsync(deviceId, userId);
  var commandResponse = await link.CommandAsync(Commands.ConnectToDevice);
}

组合asyncref的问题是,即使在方法返回后,async函数内部的代码也可以运行。所以,如果你做了这样的事情:

async Task BlockForResponseAsync(ref bool localFlag)
{
    while (!localFlag)
    {
        ...
    }
}
void SomeMethod()
{
    bool flag = false;
    BlockForResponseAsync(ref flag); // note: no await here
}

然后,在SomeMethod()返回后,局部变量flag将停止存在,但引用了该变量的BlockForResponseAsync()可能仍在执行。这就是上面的代码无法编译的原因。

基本上,你需要的是一个闭包,在C#中,ref不创建闭包,但lambdas创建。这意味着你可以这样写你的方法:

async Task BlockForResponseAsync(Func<bool> localFlagFunc)
{
    while (!localFlagFunc())
    {
        ...
    }
}

并像这样使用:

bool flag = false;
var task = BlockForResponseAsync(() => flag);
// other code here
flag = true;
await task; // to make sure BlockForResponseAsync() completed successfully

这样也能更好地表明你的意图。ref通常的意思是:"给我一个有值的变量,我会改变这个值",这不是你想要的。另一方面,Func<T>的意思是"给我一些我可以用来检索一些值的东西,可能多次"。