Запуск двух задач параллельно с обновлением GUI - PullRequest
0 голосов
/ 10 марта 2019

Я скачал пример кода для асинхронного / ожидающего в C #

https://code.msdn.microsoft.com/Async-Sample-Example-from-9b9f505c

И теперь я попытался адаптировать его для достижения другой цели: я хочу обновить графический интерфейс при выполнении GetStringAsync

Итак, это то, что я сделал, и это работает, но у меня есть некоторые сомнения по поводу моего кода. Если это правильно или «правильный» способ сделать это.

1- Использование Task.WhenAll для параллельного запуска двух заданий

2- Должен ли метод задачи UpdateUIAsync, который добавляет точку каждые 200 мс к ожидающему тексту, выполнять с помощью dispatcher.begininvoke или это нормально?

3- Совместно использовать поле finished для синхронизации поведения, опять же, «хорошо» или есть лучший подход?

public partial class MainWindow : Window
{
    // Mark the event handler with async so you can use await in it.
    private async void StartButton_Click(object sender, RoutedEventArgs e)
    {
        // Call and await separately.
        //Task<int> getLengthTask = AccessTheWebAsync();
        //// You can do independent work here.
        //int contentLength = await getLengthTask;
        finished = false;
        int[] contentLength = await Task.WhenAll(AccessTheWebAsync(), UpdateUIAsync());

        resultsTextBox.Text +=
            String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength[0]);
    }

    bool finished = false;

    // Three things to note in the signature:
    //  - The method has an async modifier. 
    //  - The return type is Task or Task<T>. (See "Return Types" section.)
    //    Here, it is Task<int> because the return statement returns an integer.
    //  - The method name ends in "Async."

    async Task<int> UpdateUIAsync()
    {
        resultsTextBox.Text = "Working ";
        while (!finished)
        {
            resultsTextBox.Text += ".";
            await Task.Delay(200);
        }
        resultsTextBox.Text += "\r\n";

        //Task<int> write = new Task<int>(() =>
        //{
        //    Dispatcher.BeginInvoke((Action)(() =>
        //    {
        //        resultsTextBox.Text = "Working ";
        //        while (!finished)
        //        {
        //            resultsTextBox.Text += ".";
        //            Task.Delay(200);
        //        }
        //        resultsTextBox.Text += "\r\n";
        //    }));

        //    return 1;
        //});

        return 1;
    }
    async Task<int> AccessTheWebAsync()
    {
        // You need to add a reference to System.Net.Http to declare client.
        HttpClient client = new HttpClient();

        // GetStringAsync returns a Task<string>. That means that when you await the
        // task you'll get a string (urlContents).
        Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

        // The await operator suspends AccessTheWebAsync.
        //  - AccessTheWebAsync can't continue until getStringTask is complete.
        //  - Meanwhile, control returns to the caller of AccessTheWebAsync.
        //  - Control resumes here when getStringTask is complete. 
        //  - The await operator then retrieves the string result from getStringTask.
        string urlContents = await getStringTask;
        finished = true;
        // The return statement specifies an integer result.
        // Any methods that are awaiting AccessTheWebAsync retrieve the length value.
        return urlContents.Length;
    }
}

Ответы [ 2 ]

1 голос
/ 11 марта 2019

1 - Использование Task.WhenAll для параллельного запуска двух Задач

Операции выполняются одновременно , а Task.WhenAll является подходящим механизмом для асинхронного параллелизма. Весь ваш код работает только в одном потоке, поэтому здесь нет никакого истинного параллелизма.

2- Должен ли метод TaskUIAsync, который добавляет точку через каждые 200 мс к ожидающему тексту, выполняться с dispatcher.begininvoke, или это нормально?

Dispatcher не требуется, поскольку код выполняется в потоке пользовательского интерфейса. Тем не менее, я рекомендую IProgress<T> шаблон , как Пауло рекомендовал , потому что это помогает сделать ваш код более тестируемым и менее привязанным к конкретному пользовательскому интерфейсу.

3- Поделиться использованием поля закончено для синхронизации поведения, опять же, «хорошо» или есть лучший подход?

В этом случае работает незащищенное общее поле, поскольку весь ваш код выполняется в одном потоке. Однако я бы порекомендовал использовать шаблон CancellationToken, чтобы семантика была более четкой: после завершения AccessTheWebAsync ваш код хочет отменить UpdateUIAsync. Использование такого установленного шаблона не только проясняет намерение, но и делает код более пригодным для повторного использования.

1 голос
/ 11 марта 2019

Способ сообщения о прогрессе async-await осуществляется через Interface"> Интерфейс IProgress и его реализацию, Class"> Класс прогресса .

Если вы измените свой UpdateUIAsync метод на:

private async Task UpdateUIAsync(IProgress<string> progress, CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        progress.Report(".");
        await Task.Delay(200);
    }
}

Тогда вы можете использовать это так:

private async void StartButton_Click(object sender, RoutedEventArgs e)
{
    this.resultsTextBox.Text = "Working ";

    using (var cts = new CancellationTokenSource())
    {
        var task = AccessTheWebAsync();
        await Task.WhenAny(
            task, 
            UpdateUIAsync(
                new Progress<string>(s => this.resultsTextBox += s),
                cts.Token));
        cts.Cancel();
        var contentLength = await task;
    }

    this.resultsTextBox.Text +=
        String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
}
...