views:

60

answers:

3

Hi!

To start with im not that experienced in Window.Forms development. However i find the InvokeRequired checks, for controls, a bit tedious when used in a threaded application. Ive created a static method that i think solves my tedious InvokeRequired checks. Just want to throw this out into the open to see if its a bad "pattern":

public static void UIInvoke(Control uiControl, Action action)
{
    if (!uiControl.IsDisposed)
    {
        if (uiControl.InvokeRequired)
        {
            uiControl.BeginInvoke(action);
        }
        else
        {
            action();
        }
    }
}

OK, so i have a textbox (named StatusTextBox) that i want to set some text to in a background thread. The code for that would be:

ThreadUtilities.UIInvoke(this.StatusTextBox, delegate()
{
    string text = this.StatusTextBox.Text;
    this.StatusTextBox.Text = (text.Length > 10) ? String.Empty : text.PadRight(1, '.');
});

Is this the same as?

this.StatusTextBox.BeginInvoke(delegate()
{
    string text = this.StatusTextBox.Text;
    this.StatusTextBox.Text = (text.Length > 10) ? String.Empty : text.PadRight(1, '.');
});

Google at its best, found this article where someone came up with same approach. Ill continue my "harassment" there instead. Thanks!

A: 

Your static UIInvoke method looks good and I cannot see anything wrong with it.

Darin Dimitrov
A: 

Google at its best, found this article where someone came up with same approach. Ill continue my "harassment" there instead. Thanks!

Mr.T
+1  A: 

I'm a firm believer that any code depending on InvokeRequired is following a poor pattern.

Each method should either know that it is running in the context of the UI thread, or know that it is not. The only methods that can be run from any thread should be on synchronization classes such as Semaphore (and if you're writing a synchronization class, ask yourself if you should really be doing that).

This is a principle of multithreaded design that reduces bugs and clarifies the code.

For the problem of updating the UI from a background thread, I recommend using a Task object scheduled to the UI's SynchronizationContext. If the Task class is not available (e.g., targeting a pre-4.0 framework), then use BackgroundWorker.

Here's an example of a background task that supports reporting progress to the UI as well as supporting cancellation and error situations:

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

class Program
{
  [STAThread]
  static void Main()
  {
    // Set up the UI and run it.

    var program = new Program
    {
      startButton = new Button
      {
        Text = "Start",
        Height = 23, Width = 75,
        Left = 12, Top = 12,
      },
      cancelButton = new Button
      {
        Text = "Cancel",
        Enabled = false,
        Height = 23, Width = 75,
        Left = 93, Top = 12,
      },
      progressBar = new ProgressBar
      {
        Width = 156, Height = 23,
        Left = 12, Top = 41,
      },
    };

    var form = new Form
    {
      Controls =
        {
          program.startButton,
          program.cancelButton,
          program.progressBar
        },
    };

    program.startButton.Click += program.startButton_Click;
    program.cancelButton.Click += program.cancelButton_Click;
    Application.Run(form);
  }

  public Button startButton;
  public Button cancelButton;
  public ProgressBar progressBar;

  private CancellationTokenSource cancellationTokenSource;

  private void startButton_Click(object sender, EventArgs e)
  {
    this.startButton.Enabled = false;
    this.cancelButton.Enabled = true;

    this.cancellationTokenSource = new CancellationTokenSource();
    var cancellationToken = this.cancellationTokenSource.Token;
    var progressReporter = new ProgressReporter();
    var task = Task.Factory.StartNew(() =>
    {
      for (int i = 0; i != 100; ++i)
      {
        // Check for cancellation
        cancellationToken.ThrowIfCancellationRequested();

        Thread.Sleep(30); // Do some work.

        // Report progress of the work.
        progressReporter.ReportProgress(() =>
        {
          // Note: code passed to "ReportProgress" may access UI elements.
          this.progressBar.Value = i;
        });
      }

      // Uncomment the next line to play with error handling. 
      //throw new InvalidOperationException("Oops..."); 

      // The answer, at last! 
      return 42;
    }, cancellationToken);

    // ProgressReporter can be used to report successful completion,
    //  cancelation, or failure to the UI thread. 
    progressReporter.RegisterContinuation(task, () =>
    {
      // Update UI to reflect completion. 
      this.progressBar.Value = 100;

      // Display results. 
      if (task.Exception != null)
        MessageBox.Show("Background task error: " + task.Exception.ToString());
      else if (task.IsCanceled)
        MessageBox.Show("Background task cancelled");
      else
        MessageBox.Show("Background task result: " + task.Result);

      // Reset UI. 
      this.progressBar.Value = 0;
      this.startButton.Enabled = true;
      this.cancelButton.Enabled = false;
    });
  }

  private void cancelButton_Click(object sender, EventArgs e)
  {
    this.cancellationTokenSource.Cancel();
  }
}

This sample code uses a ProgressReporter that I defined for convenience (it cleans up the code, IMO). This type is defined on my blog as:

/// <summary> 
/// A class used by Tasks to report progress or completion updates back to the UI. 
/// </summary> 
public sealed class ProgressReporter
{
  /// <summary> 
  /// The underlying scheduler for the UI's synchronization context. 
  /// </summary> 
  private readonly TaskScheduler scheduler;

  /// <summary> 
  /// Initializes a new instance of the <see cref="ProgressReporter"/> class. This should be run on a UI thread. 
  /// </summary> 
  public ProgressReporter()
  {
    this.scheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  /// <summary> 
  /// Gets the task scheduler which executes tasks on the UI thread. 
  /// </summary> 
  public TaskScheduler Scheduler
  {
    get { return this.scheduler; }
  }

  /// <summary> 
  /// Reports the progress to the UI thread. This method should be called from the task. Note that the progress update is asynchronous with respect to the reporting Task. For a synchronous progress update, wait on the returned <see cref="Task"/>. 
  /// </summary> 
  /// <param name="action">The action to perform in the context of the UI thread. Note that this action is run asynchronously on the UI thread.</param> 
  /// <returns>The task queued to the UI thread.</returns> 
  public Task ReportProgressAsync(Action action)
  {
    return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, this.scheduler);
  }

  /// <summary> 
  /// Reports the progress to the UI thread, and waits for the UI thread to process the update before returning. This method should be called from the task. 
  /// </summary> 
  /// <param name="action">The action to perform in the context of the UI thread.</param> 
  public void ReportProgress(Action action)
  {
    this.ReportProgressAsync(action).Wait();
  }

  /// <summary> 
  /// Registers a UI thread handler for when the specified task finishes execution, whether it finishes with success, failiure, or cancellation. 
  /// </summary> 
  /// <param name="task">The task to monitor for completion.</param> 
  /// <param name="action">The action to take when the task has completed, in the context of the UI thread.</param> 
  /// <returns>The continuation created to handle completion. This is normally ignored.</returns> 
  public Task RegisterContinuation(Task task, Action action)
  {
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.None, this.scheduler);
  }

  /// <summary> 
  /// Registers a UI thread handler for when the specified task finishes execution, whether it finishes with success, failiure, or cancellation. 
  /// </summary> 
  /// <typeparam name="TResult">The type of the task result.</typeparam> 
  /// <param name="task">The task to monitor for completion.</param> 
  /// <param name="action">The action to take when the task has completed, in the context of the UI thread.</param> 
  /// <returns>The continuation created to handle completion. This is normally ignored.</returns> 
  public Task RegisterContinuation<TResult>(Task<TResult> task, Action action)
  {
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.None, this.scheduler);
  }

  /// <summary> 
  /// Registers a UI thread handler for when the specified task successfully finishes execution. 
  /// </summary> 
  /// <param name="task">The task to monitor for successful completion.</param> 
  /// <param name="action">The action to take when the task has successfully completed, in the context of the UI thread.</param> 
  /// <returns>The continuation created to handle successful completion. This is normally ignored.</returns> 
  public Task RegisterSucceededHandler(Task task, Action action)
  {
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, this.scheduler);
  }

  /// <summary> 
  /// Registers a UI thread handler for when the specified task successfully finishes execution and returns a result. 
  /// </summary> 
  /// <typeparam name="TResult">The type of the task result.</typeparam> 
  /// <param name="task">The task to monitor for successful completion.</param> 
  /// <param name="action">The action to take when the task has successfully completed, in the context of the UI thread. The argument to the action is the return value of the task.</param> 
  /// <returns>The continuation created to handle successful completion. This is normally ignored.</returns> 
  public Task RegisterSucceededHandler<TResult>(Task<TResult> task, Action<TResult> action)
  {
    return task.ContinueWith(t => action(t.Result), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, this.Scheduler);
  }

  /// <summary> 
  /// Registers a UI thread handler for when the specified task becomes faulted. 
  /// </summary> 
  /// <param name="task">The task to monitor for faulting.</param> 
  /// <param name="action">The action to take when the task has faulted, in the context of the UI thread.</param> 
  /// <returns>The continuation created to handle faulting. This is normally ignored.</returns> 
  public Task RegisterFaultedHandler(Task task, Action<Exception> action)
  {
    return task.ContinueWith(t => action(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, this.Scheduler);
  }

  /// <summary> 
  /// Registers a UI thread handler for when the specified task becomes faulted. 
  /// </summary> 
  /// <typeparam name="TResult">The type of the task result.</typeparam> 
  /// <param name="task">The task to monitor for faulting.</param> 
  /// <param name="action">The action to take when the task has faulted, in the context of the UI thread.</param> 
  /// <returns>The continuation created to handle faulting. This is normally ignored.</returns> 
  public Task RegisterFaultedHandler<TResult>(Task<TResult> task, Action<Exception> action)
  {
    return task.ContinueWith(t => action(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, this.Scheduler);
  }

  /// <summary> 
  /// Registers a UI thread handler for when the specified task is cancelled. 
  /// </summary> 
  /// <param name="task">The task to monitor for cancellation.</param> 
  /// <param name="action">The action to take when the task is cancelled, in the context of the UI thread.</param> 
  /// <returns>The continuation created to handle cancellation. This is normally ignored.</returns> 
  public Task RegisterCancelledHandler(Task task, Action action)
  {
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, this.Scheduler);
  }

  /// <summary> 
  /// Registers a UI thread handler for when the specified task is cancelled. 
  /// </summary> 
  /// <typeparam name="TResult">The type of the task result.</typeparam> 
  /// <param name="task">The task to monitor for cancellation.</param> 
  /// <param name="action">The action to take when the task is cancelled, in the context of the UI thread.</param> 
  /// <returns>The continuation created to handle cancellation. This is normally ignored.</returns> 
  public Task RegisterCancelledHandler<TResult>(Task<TResult> task, Action action)
  {
    return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, this.Scheduler);
  }
}
Stephen Cleary
That was a bit more than i can chew right now, but thanks for the info. I will check into the classes you used. I still think its a lot code for just updating a progress, and i dont think its more readable for other developer if the where to take over the code. Why, in your opinion, is the InvokeRequired a bad pattern, do you have any sources (and dont say the google) i can read-up on the subject?
Mr.T
My prejudice against `InvokeRequired` is entirely from [personal experience](http://stackoverflow.com/questions/3179640/multithread-debugging-techniques/3213742#3213742); it's just easier to reason about the correctness of the code if that property is never used. Also note that no equivalent of `InvokeRequired` exists for any project type other than Windows Forms. WPF, Windows Services, Console programs, ASP.NET, and Silverlight all use the more modern `SynchronizationContext` abstraction (which my solution uses, too).
Stephen Cleary
Regarding the `ProgressUpdater` class: yes, it is a fair amount of code (most of it is unused in this simple example). I think it's cleaner than using `TaskScheduler` directly, and it wouldn't be a problem to pass off to another developer if it was used as part of a coding standard. I do believe that a class similar to this will become part of common usage soon.
Stephen Cleary
After reviewing your code i got flashback from another article i read a few weeks ago,http://blogs.msdn.com/b/csharpfaq/archive/2010/06/18/parallel-programming-task-schedulers-and-synchronization-context.aspx, would you say these "patters" are comparable? Ohhh and im stuck with .NET 3.5 for another year...
Mr.T
It's quite similar. The blog entry you referred to actually splits up the background task into a bunch of smaller tasks and uses continuations scheduled to the UI thread for updates. This certainly works for their example, but it doesn't work for every scenario. (Not every background task can be split into different background tasks). My approach uses the same parts (`Task` and a UI `TaskScheduler`), but uses them differently: for updates, I have the background `Task` start (and wait for) a new `Task` scheduled to the UI thread. This works in any scenario and (IMO) is slightly less complex.
Stephen Cleary
Since you're stuck in .NET 3.5, I'd recommend using `BackgroundWorker`. The [blog entry](http://nitoprograms.blogspot.com/2010/06/reporting-progress-from-tasks.html) referenced in my article shows how to convert `BackgroundWorker`-based code to `Task`-based code, with complete support for error handling, cancellation, and progress updates. If you compare the `BGW` and `Task` code samples, you can see the real purpose of `ProgressUpdater`: to help people transition from the .NET 2.0 `BGW` to the .NET 4.0 `Task`.
Stephen Cleary
Thanks for clarifying!
Mr.T