我正在尝试在异步方法回调中更新由 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 上调用Result
或Wait()
方法,因为这可能会导致死锁。不过,如果是控制台应用程序,则可以执行此操作。
如果是窗口应用程序,您应该运行略有不同的 Task.Run。您也可以使用StringBuilder
而不是string
。
我仍然想以某种方式更新该 ref 值,而不会阻止我的 UI 线程。 对我来说,这听起来不合理,这是无法做到的。
这是不可能的。
从线程的堆栈的角度考虑它。将ref
参数传递给方法时,只能在该堆栈帧(或更低帧)中写入该参数。同时,异步代码通过在操作完成之前返回到调用方法来工作。所以这里有一个不可逾越的鸿沟:必须保留堆栈帧才能写入ref
,并且堆栈帧必须弹出才能异步。这就是ref
与异步代码不兼容的原因。
更一般地说,ref
不是指针。它在逻辑上相似,但.NET是一种"安全"的编程语言,这就是为什么存在"ref
必须存在于堆栈帧中"规则的原因。.NET 有意防止将ref
复制到全局变量,在全局变量中,可以在弹出堆栈帧后对其进行更新。
您可以通过获取指向对象的指针并以这种方式(使用unsafe
代码)来执行一些危险的事情,但是在沿着这条路线走之前,我会考虑很长时间。
如果没有unsafe
代码,您的选项是:
- 通过使异步代码同步来保持堆栈帧。请注意,您的 UI 将被阻止。
- 通过允许代码异步来丢弃堆栈帧。请注意,该堆栈帧之外的任何代码(包括异步代码)都无法更新该
ref
参数。
当然,核心问题是Main
方法签名。这根本说不通。方法签名是同步的,它必须先返回,然后 UI 才能更新。它实际上应该有一个异步签名。