async / await работает в одном потоке - PullRequest
0 голосов
/ 29 мая 2019

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

class Program
{
    static void Main(string[] args)
    {
        DoWork().GetAwaiter().GetResult();
    }

    private static async Task DoWork()
    {
        TestForm form = new TestForm();
        form.Show();

        string s = await Task.Run(() =>
        {
            System.Threading.Thread.Sleep(5000);
            return "Plop";
        });

        if (s == "Plop")
        {
            form.Close();
        }
    }
}

Я ожидаю, что из приведенного выше кода TestForm будетотображается приблизительно 5 секунд, после чего закрывается из-за значения строки «Plop», однако все, что происходит, - это запуск задачи и оператор if никогда не достигается.Кроме того, пользовательский интерфейс TestForm не остается отзывчивым.Что не так с этим кодом?

Ответы [ 3 ]

1 голос
/ 29 мая 2019

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

private static void DoWork()
    {
        TestForm form = new TestForm();

        Task formTask = Task.Run(() => form.ShowDialog());

        Task<string> testTask = Task.Run(() =>
        {
            for (int i = 1; i < 10; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine(i.ToString());
            }

            Console.WriteLine("Background task finished");
            return "Plop";
        });
        Console.WriteLine("Waiting for background task");

        testTask.Wait();

        if (testTask.Result == "Plop")
        {
            Dispatcher.CurrentDispatcher.InvokeAsync(() => form.Close());
        }

        Console.WriteLine("App finished");
    }

Сначала выводится «Ожидание фоновой задачи», затем номер счета задачи, а затем выводится «Фоновая задача завершена» после завершения длинного процесса, а также закрывается отзывчивая форма пользовательского интерфейса

1 голос
/ 29 мая 2019

Это классический тупик. Когда ваш код ожидает, управление возвращается к основному потоку, который является ожиданием блокировки для DoWork GetResult (); Когда поток Task.Run завершен, элементы управления пытаются вернуться к основному потоку, но ожидают завершения работы DoWork. Вот почему последний оператор If никогда не выполняется.

Но кроме тупика , в вашем коде есть еще одна проблема, которая заставит ваш пользовательский интерфейс заморозить. Это метод form.Show (). Если вы удаляете все, что связано с async-await и только используйте форму, она все равно будет зависать. Проблема в том, что метод Show ожидает цикл сообщений Windows, который будет предоставлен, если вы создадите приложение Windows.Forms, но здесь вы запускаете форму из консольного приложения, которое не имеет цикла сообщений. Одним из решений будет использование form.ShowDialog, который создаст свой собственный цикл сообщений. Другое решение заключается в использовании метода System.Windows.Forms.Application.Run, который предоставляет цикл сообщений win для формы, созданной через поток пула потоков. Я могу дать вам одно возможное решение, но вам решать, как вы структурируете свой код в качестве основной причины.

static void Main(string[] args)
    {
        TestForm form = new TestForm();
        form.Load += Form_Load;

        Application.Run(form);

    }

    private static async void Form_Load(object sender, EventArgs e)
    {
        var form = sender as Form;
        string s = await Task.Run(() =>
        {
            System.Threading.Thread.Sleep(5000);
            return "Plop";
        });

        if (s == "Plop")
        {
            form?.Close();
        }
    }
0 голосов
/ 29 мая 2019

Хорошо, я пометил мой первый ответ как удаленный, поскольку то, что я положил туда, работает для WPF, а не для вас, НО в этом случае делает то, что вы просили, я попробовал это и открыл WinForm, а затем закрыл после 5 секунд, вот код:

static void Main(string[] args)
{
    MethodToRun();
}

private static async void MethodToRun()
{
    var windowToOpen = new TestForm();
    var stringValue = String.Empty;
    Task.Run(new Action(() =>
    {
        Dispatcher.CurrentDispatcher.InvokeAsync(() =>
        {
            windowToOpen.Show();
        }).Wait();
        System.Threading.Thread.Sleep(5000);
        stringValue = "Plop";
        Dispatcher.CurrentDispatcher.InvokeAsync(() =>
        {
            if (String.Equals(stringValue, "Plop"))
            {
                windowToOpen.Close();
            }
        }).Wait();
    })).Wait();
}
...