0

I"m trying to update a value, passed to me by ref, in an async method callback.

// 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.
}

Now, I know you can not pass ref values to async methods. But I would still like to update that ref value, somehow, without blocking my UI thread. sounds unreasonable, to me, that it can not be done.

What I tried:

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

Obviously this won't work due to not being able to the msg parameter out of scope for async method lambda expressions. My question is: what will? How can this be achieved?

Is it possible to set a global static variable which will hold the ref param, and use that in the callback instead ?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 2
    Is there any reasoning behind `Main` being `void` instead of `Task`? – V0ldek Jul 08 '19 at 08:51
  • its part of a whole RAD application (low-code), which uses .net, that's why I mentioned the entry method can not be changed, its given to you. – microsc Jul 08 '19 at 08:54

4 Answers4

0

You want mutation. string is immutable. One solution is to create a new mutable container for the 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));
}
canton7
  • 37,633
  • 3
  • 64
  • 77
0

You are still calling Foo synchronously. I would try something like this:

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;
}

Now, we don't know if it's a console or window application. If it's a window application you should not call Result or Wait() method on Task, because it could result in a deadlock. You can do this, if it's console application though.

If it's window application you should run Task.Run slightly different. You could also use StringBuilder instead of string.

phuzi
  • 12,078
  • 3
  • 26
  • 50
Adam Jachocki
  • 1,897
  • 1
  • 12
  • 28
0

Well, I see no reason why you can't just compute the answer separately and then assign to the ref value at the end:

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

Just remember to add ConfigureAwait(false) everywhere to avoid a deadlock.

V0ldek
  • 9,623
  • 1
  • 26
  • 57
0

I would still like to update that ref value, somehow, without blocking my UI thread. sounds unreasonable, to me, that it can not be done.

This is not possible.

Think about it in terms of the thread's stack. When a ref parameter is passed to a method, the parameter can only be written to in that stack frame (or below). Meanwhile, asynchronous code works by returning to the calling method before the operation is complete. So there's an impassible chasm there: the stack frame must be preserved to write to the ref, and the stack frame must be popped to be asynchronous. This is why ref is incompatible with asynchronous code.

More generally, a ref is not a pointer. It is logically similar, but .NET is a "safe" programming language, and that's why there's the "ref must live in a stack frame" rule. .NET deliberately prevents things like copying a ref to a global variable where it can be updated after that stack frame is popped.

There is the possibility that you may be able to do something Dangerous by getting a pointer to the object and manipulating it that way (using unsafe code), but I would think long and hard before going down that route.

Without unsafe code, your options are:

  1. Keep the stack frame by making the asynchronous code synchronous. Note that your UI will be blocked.
  2. Discard the stack frame by allowing the code to be asynchronous. Note that any code outside that stack frame (including asynchronous code) cannot update that ref parameter.

Of course, the core problem is the Main method signature. It just doesn't make sense. The method signature is synchronous, and it must return before the UI can update anyway. It should really have an asynchronous signature.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810