【问题标题】:InvokeRequired method - codereview helpInvokeRequired 方法 - codereview 帮助
【发布时间】:2010-07-15 18:43:53
【问题描述】:

首先我在 Window.Forms 开发方面没有经验。但是,我发现 InvokeRequired 检查控件在线程应用程序中使用时有点乏味。我创建了一个静态方法,我认为它可以解决我乏味的 InvokeRequired 检查。只是想把它公开,看看它是否是一个糟糕的“模式”:

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

好的,所以我有一个文本框(名为 StatusTextBox),我想在后台线程中设置一些文本。代码是:

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

和这个一样吗?

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

最好的谷歌发现this article 有人提出了相同的方法。我会在那里继续我的“骚扰”。谢谢!

【问题讨论】:

  • 看起来一样...你有什么问题吗?
  • 问题是我不能 100% 确定这是一个好方法,所以我想要建议或反馈。
  • 不,没问题,一切正常。只是想弄清楚为什么我看到“大量”文章使用 methodinvokers 而不是上述两种方法。正如我所说的“我在 Window.Forms 开发方面没有那么丰富的经验”。
  • Google 处于最佳状态,发现 this article 有人提出了相同的方法。我会在那里继续我的“骚扰”。谢谢!

标签: c# multithreading


【解决方案1】:

我坚信任何依赖于InvokeRequired 的代码都遵循着糟糕的模式。

每个方法都应该知道它正在UI线程的上下文中运行,或者知道它不是。可以从 any 线程运行的唯一方法应该是同步类,例如 Semaphore(如果您正在编写同步类,请问问自己是否真的应该这样做)。

这是多线程设计的一个原则,可以减少错误并澄清代码。

对于从后台线程更新 UI 的问题,我建议使用调度到 UI 的SynchronizationContextTask 对象。如果 Task 类不可用(例如,针对 4.0 之前的框架),则使用 BackgroundWorker

这是一个支持向 UI 报告进度以及支持取消和错误情况的后台任务示例:

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();
  }
}

此示例代码使用我为方便而定义的ProgressReporter(它清理了代码,IMO)。此类型为defined on my blog

/// <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);
  }
}

【讨论】:

  • 这有点超出我现在可以咀嚼的程度,但感谢您提供的信息。我将检查您使用的课​​程。我仍然认为它只是更新进度的大量代码,而且我认为如果在哪里接管代码,它对其他开发人员来说更具可读性。为什么,在您看来,InvokeRequired 是一个不好的模式,您有什么资料来源(不要说谷歌)我可以阅读这个主题吗?
  • 我对@9​​87654333@的偏见完全来自personal experience;如果从不使用该属性,则更容易推断代码的正确性。另请注意,对于 Windows 窗体以外的任何项目类型,不存在等效的 InvokeRequired。 WPF、Windows 服务、控制台程序、ASP.NET 和 Silverlight 都使用更现代的 SynchronizationContext 抽象(我的解决方案也使用)。
  • 关于ProgressUpdater 类:是的,它是相当多的代码(在这个简单的例子中大部分没有使用)。我认为它比直接使用TaskScheduler 更干净,如果将其用作编码标准的一部分,将其传递给其他开发人员也不会成为问题。我相信与此类似的类很快就会成为常见用法的一部分。
  • 在查看了您的代码后,我从几周前阅读的另一篇文章中得到了闪回,blogs.msdn.com/b/csharpfaq/archive/2010/06/18/…,您认为这些“模式”具有可比性吗?哦,我又坚持使用 .NET 3.5 一年...
  • 非常相似。您提到的博客条目实际上将后台任务拆分为一堆较小的任务,并使用调度到 UI 线程的延续进行更新。这当然适用于他们的示例,但并不适用于所有场景。 (并非每个后台任务都可以拆分为不同的后台任务)。我的方法使用相同的部分(Task 和 UI TaskScheduler),但使用方式不同:对于更新,我有背景 Task 启动(并等待)一个新的 Task 计划到 UI 线程。这适用于任何情况,并且(IMO)稍微不那么复杂。
【解决方案2】:

您的静态 UIInvoke 方法看起来不错,我看不出有什么问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多