在Async方法回调中更新REF参数

时间:2019-07-08 08:45:44

标签: c# asynchronous

我正在尝试更新异步方法回调中由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.
}

现在,我认识你can not pass ref values to async methods。但是我仍然想以某种方式更新该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参数用于异步方法lambada表达式,因此无法使用。我的问题是。会怎样。如何做到这一点。

是否可以设置一个包含引用参数的全局静态变量,并在回调中使用它?

4 个答案:

答案 0 :(得分:0)

您想要突变。 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));
}

答案 1 :(得分:0)

您仍在同步呼叫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

答案 2 :(得分:0)

好吧,我认为您没有理由不能只单独计算答案,然后最后将其分配给.mat-snack-bar-container{ white-space: pre-line !important; } 值:

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"; } 即可避免死锁。

答案 3 :(得分:0)

  

我仍然想以某种方式更新该ref值,而不会阻塞我的UI线程。在我看来,这是不合理的。

这不可能。

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

通常,ref不是指针。它在逻辑上相似,但是.NET是一种“安全”的编程语言,这就是为什么存在“ ref必须存在于堆栈框架中”规则的原因。 .NET故意防止将ref复制到全局变量,例如在弹出堆栈框架后可以对其进行更新的情况。

通过获取指向对象的指针并以这种方式进行操作(使用unsafe代码),您可能能够做一些危险的事情,但是在走那条路线之前,我会想很久很努力。

没有unsafe代码,您的选择是:

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

当然,核心问题是Main方法签名。只是没有意义。方法签名是同步的,并且必须返回,然后UI才能进行更新。它确实应该具有异步签名。