Как проще всего выполнять фоновые задачи в Windows.Forms? - PullRequest
3 голосов
/ 06 октября 2009

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

Простое значение, означающее, что при открытии файла Form.cs исходный код читается легче или легче, чем раньше. На практике поток исходного кода должен по-прежнему считываться последовательно с учетом порядка выполнения кода, независимо от того, в каком потоке он выполняется. Вся поддерживающая ткань должна быть повторно использована и спрятана где-то, а не включена в каждую форму.

Поиск в сети MSDN: обнаружено, что решение, официально санкционированное Microsoft, - это System.ComponentModel.BackgroundWorker, которое (очень!) Не соответствует второй точке.

(Существует также официально разрешенная модель решения Silverlight / XAML / 3.5 в System.Windows.Threading.Dispatcher.)

Ответы [ 8 ]

3 голосов
/ 06 октября 2009

Если вам действительно не нравится BackgroundWorker, вы можете создать свой собственный базовый класс для фоновых действий, как я сделал здесь .

2 голосов
/ 06 октября 2009

Используйте BackgroundWorker для этого

2 голосов
/ 06 октября 2009

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

Однако это мало чем отличается от простой настройки рабочего места для фоновой задачи при необходимости.

1 голос
/ 06 октября 2009

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

Включает помощника, который имеет два основных метода: Background () и Foreground (). Любой из них принимает делегата, указанного в форме лямбда-выражения. Background () запускает любой заданный делегат в фоновом потоке и немедленно возвращается. Foreground () отправляет любой заданный делегат «назад» в поток GUI с помощью Form.BeginInvoke () и немедленно возвращает.

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

public class Form1 : Form {
    protected ProgressBar progressBar1;
    protected Button button1;

    protected BackgroundHelper helper = new BackgroundHelper();

    public void button1_Click(...) {
        // Execute code in the background.
        helper.Background(() => {
            for (int i = 0; i <= 100; i++) {
                // Continually report progress to user.
                helper.Foreground<int>(i, j => {
                    progressBar1.Value = j;
                });
                // Simulate doing I/O or whatever.
                Thread.Sleep(25);
            }
        });
    }
}

Это сохраняет код аккуратно последовательным, обеспечивает общие переменные в хорошем месте и допускает циклы, которые охватывают два потока.

Чтобы уточнить, что делает помощник,

  • Конструктор запускает фоновый поток, который ожидает в очереди.
  • И Background (), и Foreground () возвращаются немедленно.
  • Background () ставит код в очередь для выполнения в фоновом потоке, используя внутреннюю очередь.
  • Foreground () делает то же самое с BeginInvoke в потоке GUI, который первым создал помощника.

РЕДАКТИРОВАТЬ: Выполнение:
http://code.google.com/p/backgrounder/

1 голос
/ 06 октября 2009

Можете ли вы объяснить, почему вы говорите, что BackgroundWorker не дотягивает?

В большинстве случаев требуется 2-3 дополнительных строки кода.

0 голосов
/ 02 декабря 2014

Как довольно убедительно доказано здесь , Microsoft увидела недостатки в BackgroundWorker, и вы можете получить намного лучший, более понятный код, используя Task.Run с ключевым словом async.

0 голосов
/ 07 октября 2009

Альтернативой для выполнения фоновых задач в Windows Forms и вызова событий после их завершения является использование классов AsyncOperationManager и AsyncOperation . Здесь следует отметить, что AsyncOperation должен быть создан в потоке пользовательского интерфейса для правильной работы. В противном случае код пользовательского интерфейса должен проверить InvokeRequired.

public class BackgroundTask{

  private AsyncOperation _asyncOperation;
  public EventHandler Done;

  public BackgroundTask(){
    _asyncOperation = AsyncOperationManager.CreateOperation();
  }

  public void DoAsync(object userState) {
    System.Threading.ThreadPool.QueueUserWorkItem( ExecuteDo, userState);
  }

  private void ExecuteDo(object state) {
    // Do your work here
    // Raise event after finish
    _asyncOperation.PostOperationCompleted( Finished, EventArgs.Empty );
  }

  private void LookUpFinished( object eventargs ) {
    OnDone( ( EventArgs) eventargs );
  }

  private void OnDone( LookUpEventArgs e ) {
    EventHandler localEvent = Done;
    if ( localEvent!= null ) {
      localEvent(this,e);
    } 
  }
}

Вот код, как его использовать:

public class MyForm : Form {

  public MyForm() {
    InitializeComponent();
  }

  protected override OnShown(EventArgs e) {
    BackgroundTask task = new BackgroundTask();
    task.Done += SignalTaskDone;
  }

  private void SignalTaskDone(object sender, EventArgs e){
    MessageBox.Show(this, "Task finished");
  }
}
0 голосов
/ 06 октября 2009

Например, после вашего вопроса дискового ввода / вывода я недавно написал класс асинхронного сканера файлов (или в сотый раз за все годы). Его можно использовать повторно, как вы хотите, просто создав новый экземпляр и подключившись к событиям.

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

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

Это демонстрирует выделение многократно используемого кода в отдельный класс ...

Public Class FileScanner
    Public Event Scan_Complete(sender As Object)
    Public Event Scan_Update(sender As Object, filename As String)
    Public Event Scan_Error(sender As Object, ex As Exception)
    Private Delegate Sub del_ScanComplete()

    Sub New(syncObject As Control, path String)
        Me.SynchronizeObject = syncObject
        ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ScanFilesAsync), path)
    End Sub

    Private Sub ScanFilesAsync(ByVal state As Object)

        ' scan files here

        ' call the method to raise the Complete event
        ScanComplete()

    End Sub

    Private Sub ScanComplete()

        If SynchronizeObject.InvokeRequired Then
            ' we cant raise event on a different thread than our caller
            ' so lets invoke our method on the same caller thread
            Dim d As New del_ScanComplete(AddressOf ScanComplete)
            SynchronizeObject.Invoke(d)
        Else
            ' no synchronize needed, tell the caller we are done
            RaiseEvent Complete(Me)
        End If

    End Sub

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