Можно ли дождаться завершения кода Device.BeginInvokeOnMainThread (продолжить фоновую работу с результатами вызова пользовательского интерфейса) - PullRequest
0 голосов
/ 07 февраля 2019

В моем коде у меня есть задача под названием «ShowMessageBoxAsync».Я хочу использовать этот код, чтобы показать (и ожидать) DisplayAlert для пользователя и вернуть результат.Например: var messageBoxResult = await View.ShowMessageBoxAsync («Это ошибка»);

Код для ShowMessageBoxAsync:

public async System.Threading.Tasks.Task<bool> ShowMessageBoxAsync(string message)
{
    var result = false;
    Device.BeginInvokeOnMainThread(async () =>
    {
        result = await DisplayAlert("Error", message, "OK", "Cancel");
    });
    return result;
}

Перед добавлением Device.BeginInvokeOnMainThread,задача дала мне исключение, что он не работал в основном потоке / пользовательском интерфейсе.Поэтому после добавления BeginInvokeOnMainThread он начал работать без исключений.Проблема, однако, заключается в том, что код переходит непосредственно к результату, не дожидаясь результата «await DisplayAlert».

Возможно ли вернуть значение «result» только после кода Device.BeginInvokeOnMainThreadзаканчивается?

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

Ответы [ 4 ]

0 голосов
/ 08 февраля 2019

Я провел некоторое исследование по этому поводу, и кто-то предложил использовать TaskCompletionSource

Это правильное решение.TaskCompletionSource действует как «завершитель» для Task.В этом случае этот Task представляет взаимодействие с пользователем, поэтому вы хотите, чтобы код в потоке пользовательского интерфейса выполнял завершение Task, а код в фоновом потоке (а) ожидал `Task.

Итак, что-то вроде этого:

public Task<bool> ShowMessageBoxAsync(string message)
{
  var tcs = new TaskCompletionSource<bool>();
  Device.BeginInvokeOnMainThread(async () =>
  {
    try
    {
      var result = await DisplayAlert("Error", message, "OK", "Cancel");
      tcs.TrySetResult(result);
    }
    catch (Exception ex)
    {
      tcs.TrySetException(ex);
    }
  });
  return tcs.Task;
}

Это должно разблокировать вас на данный момент.Однако лучшее долгосрочное решение состояло бы в том, чтобы логика фонового потока принимала некоторый интерфейс «взаимодействия с пользовательским интерфейсом», например:

public interface IAskUser
{
  Task<bool> AskUserAsync(string message);
}

с реализацией для Xamarin-Forms, аналогичной приведенной выше..

Таким образом, логика вашего фонового потока не привязана к конкретному пользовательскому интерфейсу, и ее проще тестировать и использовать повторно.

0 голосов
/ 07 февраля 2019

Если вы решите использовать C # async / await, вы должны перейти к правильному потоку перед вызовом любого async метода.Поставьте точку останова на начало метода в вашем вопросе - до того, как он вызовет BeginInvoke.Поднимайтесь по стеку вызовов, пока не получите метод, который не объявлен async. То, что - это метод , который должен выполнять BeginInvokeOnMainThread в качестве обертки вокруг всех вашего асинхронного кода.

Обязательно удалите BeginInvokeOnMainThread из кода, который вы показали - как только вы окажетесь внутри async/await, это не будет делать то, что вы хотите.


ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Было бы лучше вместо этого сделать фундаментальное изменение в другом месте.
Почему вы находитесь в фоновом потоке, но вам требуется вызов пользовательского интерфейса?

Существует множество механизмов разделенияВаш код в часть пользовательского интерфейса и часть фона: вы еще не сделали этого полностью, иначе у вас не возникнет этой проблемы.

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

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

Или ранее в вашем коде, прежде чем начинать фоновое задание, вам нужно было проверить, что вы получили всю необходимую информацию от пользователя, и связаться с ней.

Если это не такЕсли намеков на то, чтобы направить вас в рабочее русло, я рекомендую найти более полный пример async / await, используемого в приложении, изучить его работу.Если у вас все еще есть этот вопрос, добавьте к нему более подробную информацию.


Это «чистый» ответ.

На практике мне иногда бывает проще добавить BeginInvokeOnMainThread - и затем напишите код без асинхронности / ожидания .

Г.Хаким сделал существенное наблюдение:

DisplayAlert являетсяМетод возврата задачи ...

См. Документация Microsoft - Цепочка задач с помощью задач продолжения Если вы хотите понять, как вызывать DisplayAlert, затем выполните что-то еще.

Идя по этому пути, я думаю, вам нужно удалить async и await из вашего метода.Вы будете выполнять «асинхронный» вызов самостоятельно.
Вместо того, чтобы пытаться выполнить работу после возвращения этого метода, вы передадите параметр с работой, которая будет выполнена дальше.Что-то вроде:

public void ShowMessageBoxAsync(string message, Action afterAction)
{
    Device.BeginInvokeOnMainThread(() =>
    {
        // See "Chaining Tasks ..." link. Use "afterAction" as "continuation".
        ... DisplayAlert("Error", message, "OK", "Cancel") ...
    });
}

«afterAction» - это то, что станет «продолжением».
Извините, у меня нет подробностей - см. Ссылку выше.

0 голосов
/ 08 февраля 2019

Вы можете сделать это, используя ManualResetEvent, например:

var mre = new ManualResetEvent(false);
Device.BeginInvokeOnMainThread(() =>
{
    // do whatever
    mre.Set();
});
mre.WaitOne();
//continues when the UI thread completes the work
0 голосов
/ 07 февраля 2019

Быть очень честным в моих Знаниях, то, что вы пытаетесь достичь здесь, невозможно.

Потому что BeginInvokeOnMainThread - это пустой метод, который выполняет действие, и оба они ничего не могут вернуть.

Но мне любопытно, что здесь еще что-то: DisplayAlert - это Task<bool> метод возврата, тогда почему вы хотите обернуть его в другой Task<bool>?Разве вы не можете просто использовать его там, где вам это нужно, я имею в виду, что вполне возможно сделать это, не так ли?

Не стесняйтесь вернуться в случае запросов

Goodluck!

...