在异步方法回调中更新 REF 参数



我正在尝试在异步方法回调中更新由 ref 传递给我的值。

// this Main method parameters are given and can not be changed.
public static void Main(ref System.String msg)
{
// here we should invoke an async code,
// which updates the msg parameter.
}

现在,我知道您不能将 ref 值传递给异步方法。但我仍然想以某种方式更新该 ref 值,而不会阻止我的 UI 线程。 对我来说,这听起来不合理,这是无法做到的。

我尝试过:

// Our entry point
public static void Main(ref System.String msg)
{
Foo(msg);
}
// calls the updater (can't use 'await' on my entry point. its not 'async method')
static async void Foo(ref System.String m)
{
var progress = new Progress<string>(update => { m = update; });
await Task.Run(() => MyAsyncUpdaterMethod(progress));
}
// update the variable
static void MyAsyncUpdaterMethod(IProgress<string> progress)
{
Thread.Sleep(3000);
if (progress != null)
{
progress.Report("UPDATED");
}
}

显然,由于无法将msg参数超出异步方法 lambda 表达式的范围,因此这不起作用。我的问题是:什么会?如何实现这一点?

是否可以设置一个全局静态变量来保存 ref 参数,并在回调中使用它?

你想要突变。string是不可变的。一种解决方案是为字符串创建新的可变容器。

public class StringContainer
{
public string String { get; set; }
}
static async void Foo(StringContainer container)
{
var progress = new Progress<string>(update => container.String = update);
await Task.Run(() => MyAsyncUpdaterMethod(progress));
}

好吧,我认为没有理由不能单独计算答案,然后在最后分配给ref值:

public static void Main(ref System.String msg)
{
var result = Foo(msg).GetAwaiter().GetResult();
msg = result;
}
private static async Task<string> Foo(string msg)
{   
await Task.Delay(3000).ConfigureAwait(false);
return "UPDATED";
}

只需记住在任何地方添加ConfigureAwait(false)以避免死锁。

您仍在同步调用Foo。我会尝试这样的事情:

public static void Main(ref string msg)
{
msg = Foo(msg);
}
public static string Foo(string msg)
{
return Task.Run(async ()=> await DoAsyncWork(msg)).Result;
}
async Task<string> DoAsyncWork(string msg)
{
string result = await DoMaybeSomeOtherTask(msg);
return result;
}

现在,我们不知道它是控制台还是窗口应用程序。如果是窗口应用程序,则不应在 Task 上调用ResultWait()方法,因为这可能会导致死锁。不过,如果是控制台应用程序,则可以执行此操作。

如果是窗口应用程序,您应该运行略有不同的 Task.Run。您也可以使用StringBuilder而不是string

我仍然想以某种方式更新该 ref 值,而不会阻止我的 UI 线程。 对我来说,这听起来不合理,这是无法做到的。

这是不可能的。

从线程的堆栈的角度考虑它。将ref参数传递给方法时,只能在该堆栈帧(或更低帧)中写入该参数。同时,异步代码通过在操作完成之前返回到调用方法来工作。所以这里有一个不可逾越的鸿沟:必须保留堆栈帧才能写入ref,并且堆栈帧必须弹出才能异步。这就是ref与异步代码不兼容的原因。

更一般地说,ref不是指针。它在逻辑上相似,但.NET是一种"安全"的编程语言,这就是为什么存在"ref必须存在于堆栈帧中"规则的原因。.NET 有意防止将ref复制到全局变量,在全局变量中,可以在弹出堆栈帧后对其进行更新。

您可以通过获取指向对象的指针并以这种方式(使用unsafe代码)来执行一些危险的事情,但是在沿着这条路线走之前,我会考虑很长时间。

如果没有unsafe代码,您的选项是:

  1. 通过使异步代码同步来保持堆栈帧。请注意,您的 UI 将被阻止。
  2. 通过允许代码异步来丢弃堆栈帧。请注意,该堆栈帧之外的任何代码(包括异步代码)都无法更新该ref参数。

当然,核心问题是Main方法签名。这根本说不通。方法签名是同步的,它必须先返回,然后 UI 才能更新。它实际上应该有一个异步签名。

相关内容

  • 没有找到相关文章

最新更新