Подтвердить что ты не робот

Async/Await с WinForms ProgressBar

Я использовал этот тип работы в прошлом с 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;
        }
    }
}
}
4b9b3361

Ответ 1

@Ответ на STEPENCLEAR правильный. Хотя, я должен был внести небольшую модификацию в свой ответ, чтобы получить поведение, о котором я думаю, что хочет 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);
}

Ответ 2

Ключевые слова async и await не означают "запускать в фоновом потоке". В моем блоге есть вступление async/await которое описывает, что они значат. Вы должны явно размещать связанные с процессором операции в фоновом потоке, например, 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 в Form1.UpdateProgress.

Ответ 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);
    });
}

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