Не могу понять, как использовать Progress (T) для обновления пользовательского интерфейса во время асинхронного процесса - PullRequest
0 голосов
/ 14 декабря 2018

Я гуглял по этому вопросу довольно много часов и прочитал немало ТАК вопросов, где это обсуждается, но мне жаль говорить, что я просто не понимаю, как его использовать.

По сути, чтоЯ пытаюсь сделать так, чтобы ярлык в приложении WPF / Win Forms отображал следующее во время выполнения асинхронной задачи:

Обработка.

и на каждомИнтервал в 1 секунду, чтобы добавить еще одну точку, пока я не достигну трех, а затем начать с 1, пока задача не будет выполнена.

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

Затем я попытался выполнитьследующее:

   private async void startButton_Click(object sender, RoutedEventArgs e)
   {
        resultsTextBox.Text = "Waiting for the response ...";

        startButton.IsEnabled = false;
        resultsTextBox.Clear();

        var task = SumPageSizesAsync();

        var progress = Task.Run(() =>
        {
           var aTimer = new System.Timers.Timer(1000);
           aTimer.Elapsed += OnTimedEvent;
           aTimer.AutoReset = true;
           aTimer.Enabled = true;

           void OnTimedEvent(object source, ElapsedEventArgs et)
           {
               if (!lblProgress.Dispatcher.CheckAccess())
               {
                   Dispatcher.Invoke(() =>
                   {
                       lblProgress.Content += ".";
                   });
               }
           }
       });

       await task;
       await progress;

       resultsTextBox.Text += "\r\nControl returned to startButton_Click.";

       startButton.IsEnabled = true;
}

Но опять же, метка сразу заполняется точками, в то время как другая задача продолжает выполняться.

Я взял этот пример из Документов Microsoft

ОБНОВЛЕНИЕ : Я попытался удалить цикл while (! Task.IsComplete), который в основном заставляет метку начинать обновляться после завершения первой задачи.Затем я попытался сделать следующее:

var task = SumPageSizesAsync();
var progress = GetProgress();

await Task.WhenAll(SumPageSizesAsync(), GetProgress());

Но получил тот же результат, ярлык начинает обновляться после завершения первого задания.

Спасибо за вашу помощь.

Ответы [ 3 ]

0 голосов
/ 15 декабря 2018

Если вы используете await, это означает, что ваш код будет ожидать завершения асинхронной операции в этой строке, а затем продолжит работу.Вот почему ваша задача progress не запускается до тех пор, пока задача task не будет завершена.Вы можете создать фоновый поток, который будет работать параллельно с task, пока он не будет завершен, и там вы можете указать потоку пользовательского интерфейса анимировать точки один раз в секунду.Поскольку поток пользовательского интерфейса НЕ заблокирован (а только ожидает завершения task), это работает.

Пример кода:

string originalLblContent = (lblProgress.Content as string) ?? "";
bool taskStarted = false;

var progressThread = new Thread((ThreadStart)delegate
{
    // this code will run in the background thread
    string dots = "";
    while(!taskStarted || !task.IsCompleted) {
        if(dots.Length < 3) {
            dots += ".";
        } else {
            dots = "";
        }
        // because we are in the background thread, we need to invoke the UI thread
        // we can invoke it because your task is running asynchronously and NOT blocking the UI thread
        Dispatcher.Invoke(() =>
        {
            lblProgress.Content = originalLblContent + dots;
        });
        Thread.Sleep(1000);
    }
});

progressThread.Start();
taskStarted = true;
await task;

// the task is now finished, and the progressThread will also be after 1 second ...
0 голосов
/ 15 декабря 2018

"Прогресс (T)" - неправильный шаблон для этого.

Вот код для приложения WPF, которое делает это с кодом 100% async / await, дополнительные потоки не создаются.

Запускает две async задачи.Первый имитирует длительный процесс async.Второй запускает другой async Task, который принимает первое задание в качестве параметра.Он зацикливается до тех пор, пока не будет выполнено первое задание, при обновлении метки шаблоном «...».Ожидается Task.Delay для управления скоростью анимации.

Обе эти задачи помещаются в список, и мы await завершим обе из них.

Это может всеОберните его в ShowProgressUntilTaskCompletes метод (или метод расширения), который принимает рабочий Task в качестве параметра, который дает вам многократно используемый метод отображения индикатора прогресса для любого Task.

MainWindow.xaml:

<Window
    x:Class="LongProcessDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <StackPanel Margin="100" Orientation="Vertical">
        <Button Click="StartProcess_OnClick" Content="Start" />
        <TextBlock
            Name="LoadingText"
            Padding="20"
            Text="Not Running"
            TextAlignment="Center" />
    </StackPanel>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;

namespace LongProcessDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void StartProcess_OnClick(object sender, RoutedEventArgs e)
        {
            var longRunningTask = SimulateLongRunningTask();
            var spinner = ShowSpinner(longRunningTask);
            var tasks = new List<Task>
            {
                longRunningTask,
                spinner,
            };
            await Task.WhenAll(tasks);
        }

        private async Task ShowSpinner(Task longRunningTask)
        {
            var numDots = 0;
            while (!longRunningTask.IsCompleted)
            {
                if (numDots++ > 3) numDots = 0;
                LoadingText.Text = $"Waiting{new string('.', numDots)}";
                await Task.Delay(TimeSpan.FromSeconds(.5));
            }

            LoadingText.Text = "Done!";
        }

        private async Task SimulateLongRunningTask()
        {
            await Task.Delay(TimeSpan.FromSeconds(10));
        }
    }
}

Вот его запись, с взаимодействием окон, доказывающим, что пользовательский интерфейсне заблокирован.

enter image description here


В качестве дополнительного бонуса мне стало скучно, и я применил упомянутый мной метод расширения (с super специальный бонус, функция «локальной функции» из C # 7!).

public static class TaskExtensions
{
    public static async Task WithSpinner(this Task longRunningTask, TextBlock spinnerTextBox)
    {
        async Task ShowSpinner()
        {
            var numDots = 0;
            while (!longRunningTask.IsCompleted)
            {
                if (numDots++ > 3) numDots = 0;
                spinnerTextBox.Text = $"Waiting{new string('.', numDots)}";
                await Task.Delay(TimeSpan.FromSeconds(.5));
            }
            spinnerTextBox.Text = "Done!";
        }

        var spinner = ShowSpinner();
        var tasks = new List<Task>
        {
            longRunningTask,
            spinner,
        };
        await Task.WhenAll(tasks);
    }
}

Вы используете это так:

await SimulateLongRunningTask().WithSpinner(LoadingTextBlock);
0 голосов
/ 15 декабря 2018

Ваш подход здесь немного прикольный.Операторы await будут препятствовать возврату метода до завершения каждого потока.Функция await не является полностью асинхронным выполнением (с чего бы это? У вас для этого есть потоки).

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

От Microsoft:

"Обработка операций блокировки в графическом приложении может быть затруднена. Мы не хотим вызывать методы блокировки из обработчиков событий, поскольку приложение будет зависать. Мы можем использовать отдельный поток для обработки этих операций, но когда мы закончим, мы должны синхронизироваться с потоком пользовательского интерфейса, потому что мы не можем напрямую изменить графический интерфейс из нашего рабочего потока. Мы можем использовать Invoke или BeginInvokeдля вставки делегатов в Dispatcher потока пользовательского интерфейса. В конечном итоге эти делегаты будут выполняться с разрешением на изменение элементов пользовательского интерфейса.

В этом примере мы имитируем удаленный вызов процедуры, который получает прогноз погоды. Мы используемотдельный рабочий поток для выполнения этого вызова, и мы планируем обновить метод в Dispatcher потока пользовательского интерфейса, когда закончим. "

enter image description here

https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-model

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...