Async / Ожидание с помощью WinForms ProgressBar

17

Я использовал этот тип работы в прошлом с BackgroundWorker, но я хочу использовать новый подход async / wait для .NET 4.5. Я могу лаять неправильное дерево. Просьба сообщить.

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

Статус : см. код ниже. Я думал, что у меня все хорошо, пока я не попытался взаимодействовать с окнами. Если я оставляю вещи в покое (то есть не трогаю!), Все работает «отлично», но если я делаю так много, как нажимать на любое окно, программа зависает после завершения длительной работы. Фактические взаимодействия (перетаскивание) игнорируются, как если бы поток пользовательского интерфейса блокировался.

Вопросы . Может ли мой код исправляться довольно легко? Если да, то как? Или, должен ли я использовать другой подход (например, BackgroundWorker)?

Код (Form1 - стандартная форма с ProgressBar и общедоступным методом UpdateProgress, который устанавливает значение ProgressBar):

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ConsoleApplication1
{
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Starting..");
        var mgr = new Manager();
        mgr.GoAsync();
        Console.WriteLine("..Ended");
        Console.ReadKey();
    }
}

class Manager
{
    private static Form1 _progressForm;

    public async void GoAsync()
    {
        var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
        _progressForm = new Form1();
        _progressForm.Show(owner);

        await Go();

        _progressForm.Hide();
    }

    private async Task<bool> Go()
    {
        var job = new LongJob();
        job.OnProgress += job_OnProgress;
        job.Spin();
        return true;
    }

    void job_OnProgress(int percent)
    {
        _progressForm.UpdateProgress(percent);
    }
}

class LongJob
{
    public event Progressed OnProgress;
    public delegate void Progressed(int percent);

    public void Spin()
    {
        for (var i = 1; i <= 100; i++)
        {
            Thread.Sleep(25);
            if (OnProgress != null)
            {
                OnProgress(i);
            }
        }
    }
}

class Win32Window : IWin32Window
{
    private readonly IntPtr _hwnd;
    public Win32Window(IntPtr handle)
    {
        _hwnd = handle;
    }
    public IntPtr Handle
    {
        get
        {
            return _hwnd;
        }
    }
}
}
    
задан Todd Sprang 31.07.2013 в 15:49
источник

3 ответа

7

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

public void GoAsync() //no longer async as it blocks on Appication.Run
{
    var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
    _progressForm = new Form1();

    var progress = new Progress<int>(value => _progressForm.UpdateProgress(value));

    _progressForm.Activated += async (sender, args) =>
        {
            await Go(progress);
            _progressForm.Close();
        };

    Application.Run(_progressForm);
}
    
ответ дан YK1 31.07.2013 в 17:31
источник
  • , если Go (прогресс) выдает исключение, тогда _progressForm.Close () никогда не будет вызываться -> модальный диалог будет зависать вечно –  Hiep 19.01.2016 в 17:44
  • И лучше изменить «_progressForm.Activated» на «_progressForm .Shown», поскольку _progressForm может быть активирован несколько раз в течение его жизненного цикла .. -> Go (progress) будет вызываться несколько раз. –  Hiep 19.01.2016 в 17:56
  • gist.github.com/duongphuhiep/f83f98593d93045e717f –  Hiep 19.01.2016 в 18:12
18

Ключевые слова async и await не означают «запустить фоновый поток». У меня есть async / await intro в моем блоге , в котором описывается, что они do означает. Вы должны явно помещать операции с привязкой к ЦП в фоновом потоке, например, Task.Run .

Кроме того, в документации Асинхронный шаблон на основе задач описаны общие подходы с async код, например, отчет о ходе выполнения.

class Manager
{
  private static Form1 _progressForm;

  public async Task GoAsync()
  {
    var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
    _progressForm = new Form1();
    _progressForm.Show(owner);

    var progress = new Progress<int>(value => _progressForm.UpdateProgress(value));
    await Go(progress);

    _progressForm.Hide();
  }

  private Task<bool> Go(IProgress<int> progress)
  {
    return Task.Run(() =>
    {
      var job = new LongJob();
      job.Spin(progress);
      return true;
    });
  }
}

class LongJob
{
  public void Spin(IProgress<int> progress)
  {
    for (var i = 1; i <= 100; i++)
    {
      Thread.Sleep(25);
      if (progress != null)
      {
        progress.Report(i);
      }
    }
  }
}

Обратите внимание, что тип Progress<T> правильно обрабатывает маршалинг потоков, поэтому нет необходимости в маршалинге внутри Form1.UpdateProgress .

    
ответ дан Stephen Cleary 31.07.2013 в 16:33
источник
  • Ваши изменения не дают желаемых результатов. Поскольку OP работает в приложении Console, SynchronizationContext отсутствует. Я думаю, что для Progress <T> необходимо правильно работать? –  YK1 31.07.2013 в 17:10
  • Компоненты Windows Forms будут создавать WinFormsSynchronizationContext при их создании, а Progress <T> не требует SynchronizationContext (хотя он работает лучше с одним). –  Stephen Cleary 31.07.2013 в 17:13
  • Я поставил точку перерыва в делетете, переданном Progress <T> - он никогда не попадает - UI просто зависает, хотя работа вращается. Я думал, что Application.Run () требуется для установки SynchronizationContext - делает _progressForm.Show установить один? Я не уверен. –  YK1 31.07.2013 в 17:18
  • Я считаю, что есть SynchronizationContext, но нет основного цикла. Главному пришлось бы вызвать Application.Run, ShowDialog или что-то в этом роде. –  Stephen Cleary 31.07.2013 в 17:23
  • Да, вы правы. SynchronizationContext есть, но нет цикла для обработки вещей. Отправлено / Отправить. Следовательно, моя точка разлома не ударит. Благодарю. –  YK1 31.07.2013 в 17:41
Показать остальные комментарии
3
private async void button1_Click(object sender, EventArgs e)
{
    IProgress<int> progress = new Progress<int>(value => { progressBar1.Value = value; });
    await Task.Run(() =>
    {
        for (int i = 0; i <= 100; i++)
            progress.Report(i);
    });
}

Исправьте меня, если я ошибаюсь, но это самый простой способ обновить индикатор выполнения.

    
ответ дан user2539367 09.10.2015 в 12:36
источник