Обновление параметра REF в обратном вызове метода Async - PullRequest
0 голосов
/ 08 июля 2019

Я пытаюсь обновить значение, переданное мне 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 как-нибудь, не блокируя мой поток пользовательского интерфейса. Для меня это звучит неоправданно, что это невозможно сделать.

То, что я пытался:

// 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 из области видимости для ламбадных экспериментов с асинхронным методом. Мой вопрос: что будет. Как этого достичь.

можно установить глобальную статическую переменную, которая будет содержать параметр ref, и вместо этого использовать ее в обратном вызове?

Ответы [ 4 ]

0 голосов
/ 08 июля 2019

Я бы все же хотел как-то обновить это значение ссылки, не блокируя мой поток пользовательского интерфейса.для меня звучит неразумно, что это невозможно сделать.

Это невозможно.

Подумайте об этом с точки зрения стека потока.Когда параметр ref передается методу, параметр может быть записан только в этот кадр стека (или ниже).Тем временем асинхронный код работает путем , возвращая вызывающему методу до завершения операции.Так что здесь непроходимая пропасть: кадр стека должен быть сохранен для записи в ref, а кадр стека должен быть вытолкнут, чтобы быть асинхронным.Вот почему ref несовместим с асинхронным кодом.

В общем случае ref не является указателем.Это логически похоже, но .NET - это «безопасный» язык программирования, и поэтому существует правило «ref должен жить в стеке»..NET преднамеренно предотвращает такие вещи, как копирование ref в глобальную переменную, где он может обновляться после извлечения кадра стека.

Существует вероятность того, что вы можете сделать что-то опасное, получив указательк объекту и манипулируя им таким образом (используя unsafe код), но я бы долго и усердно думал, прежде чем идти по этому маршруту.

Без unsafe кода ваши варианты:

  1. Сохраните кадр стека, сделав асинхронный код синхронным.Обратите внимание, что ваш пользовательский интерфейс будет заблокирован.
  2. Сбросьте кадр стека, сделав код асинхронным.Обратите внимание, что любой код вне этого стекового фрейма (включая асинхронный код) не может обновить этот параметр ref.

Конечно, основная проблема заключается в сигнатуре метода Main.Это просто не имеет смысла.Подпись метода является синхронной, и она должна вернуться, прежде чем пользовательский интерфейс все равно сможет обновить.У него действительно должна быть асинхронная подпись.

0 голосов
/ 08 июля 2019

Вы все еще звоните 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;
}

Теперь мы не знаем, является ли это консольным или оконным приложением.Если это оконное приложение, вы не должны вызывать метод Result или Wait() в Task, потому что это может привести к тупику.Вы можете сделать это, если это консольное приложение.

Если это оконное приложение, вы должны запустить Task.Run немного по-другому.Вы также можете использовать StringBuilder вместо string.

0 голосов
/ 08 июля 2019

Ну, я не вижу причин, почему вы не можете просто вычислить ответ отдельно, а затем присвоить значение 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) вездеизбежать тупика.

0 голосов
/ 08 июля 2019

Вы хотите мутации.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));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...