31

是否在应用程序System.Windows.Threading.Dispatcher的 UI 线程上工作WinForms

如果是,为什么?它来自似乎是一个WPF组件的 WindowsBase.dll。

如果没有,我如何将工作单元调用回 UI 线程?我找到了Control.BeginInvoke(),但是创建一个控件来引用原始线程似乎很笨拙。

4

7 回答 7

27

Dispatcher甚至可以在 WinForms 应用程序中使用。

如果您确定在 UI 线程上(例如在 button.Click 处理程序中),则为Dispatcher.CurrentDispatcher您提供 UI 线程分派器,您以后可以使用它像往常一样从后台线程分派到 UI 线程。

于 2009-07-21T15:42:42.020 回答
13

Dispatcher 是 WPF 组件,而不是 WinForms 组件。

如果您想在 UI 线程上分派工作项,那么您将不得不使用您已经找到的 Control.BeginInvoke,或者跨线程对 ResetEvents/WaitObjects 做出反应。

通常在 UI 线程上调用工作项是一件坏事,除非它是 UI 工作(即更新控件的内容或其他东西),在这种情况下 Control.BeginInvoke() 就足够了。

于 2008-11-19T20:17:10.923 回答
11

我在对问题“在 WinForms 上使用 TPL 进行并行编程”的回答中提供了一个System.Windows.Threading.Dispatcher在 Windows 窗体中使用的示例,因为您之前回答了您的问题

如果您确定在 UI 线程中(例如,在 button.Click 处理程序中),Dispatcher.CurrentDispatcher 为您提供 UI 线程调度程序,您可以使用它像往常一样从后台线程调度到 UI 线程。

要么具有误导性,要么令人困惑,要么缺乏具体的使用上下文:

  • button.Click处理程序不保证在 UI 线程上;
  • 如果你不在 UI 线程上,仍然可以使用 WinForms 表单的 UI 线程的调度程序

可以获取 WinForm UI 线程的调度程序:

Dispatcher dispatcherUI = Dispatcher.CurrentDispatcher;

在按钮单击事件处理程序或其他任何地方(在表单构造函数中)

然后使用它在其他线程的 UI 上执行,在我的答案中查看下面示例的更多详细信息:

private void button1_Click(object sender, EventArgs e)
{
  Dispatcher dispUI = Dispatcher.CurrentDispatcher;
  for (int i = 2; i < 20; i++)
  {
    int j = i;
    var t = Task.Factory.StartNew
           (() =>
      {
        var result = SumRootN(j);
        dispUI.BeginInvoke
            (new Action
                 (() => richTextBox1.Text += "root " + j.ToString()
                       + " " + result.ToString() + Environment.NewLine
                 )
             , null
            );
      }
           );
}
于 2013-04-15T04:30:26.543 回答
1

我在使用在 Winforms 中自己的线程上运行的 Oracle 依赖类时遇到了类似的问题,

当从 Oracle Dependency 触发 OnChange 事件时,我想通过简单地将 DataSource 设置为 eventargs.Details(本质上是一个 DataTable)来显示 DataGridView 中的更改,并且它抛出: System.InvalidOperationException is unhandled by user code Message=Cross-thread operation无效:控件“dataGridView1”从创建它的线程以外的线程访问。

StackOverflow 用户 Brian Peiris (bpeiris@gmail.com),我的同事向我展示了这种方式:

void dep_OnChange(object sender, OracleNotificationEventArgs arg)
         {
         Console.WriteLine("Notification received");

         int infoSum = int.Parse(arg.Details.Compute("Sum(Info)", "Info is not null").ToString());
         InfoSum x = (InfoSum)infoSum;
         foreach (DataRow dr in arg.Details.Rows)
            {
            Console.WriteLine(string.Format("Operation(InfoSum)= {0}", Enum.GetName(typeof(InfoSum), x)));
            Console.WriteLine(string.Format("ontable={0}  Rowid={1},info={2}", dr.Field<string>("ResourceName"), dr.Field<string>("rowid"), dr.Field<Int32>("info")));
            }
         // Following  will throw cross-thread 
         // dataGridView1.DataSource = arg.Details;
         // instead of line above use the following
         dataGridView1.BeginInvoke((Action)(()=>dataGridView1.DataSource = arg.Details));
         IsNotified = true;
         }

      }
于 2012-09-27T20:29:34.340 回答
1

使用后台工作线程,因为它可以感知 UI 消息泵,这篇 MSDN 文章虽然主要是关于 WPF 的,但确实指出 BWT 是 UI 感知的,即使对于 Windows 窗体也是如此。

于 2008-11-19T20:31:50.333 回答
0

有时 Timer 组件很有用且易于在 WinForms 中设置,只需设置其间隔然后启用它,然后确保您在其 Tick 事件处理程序中做的第一件事就是禁用它自己。

我认为 Timer 在其自己的线程中运行代码,因此您可能仍需要执行 BeginInvoke(调用 WinForm 对象 [this])来运行您的 Action。

private WebBrowserDocumentCompletedEventHandler handler; //need to make it a class field for the handler below (anonymous delegates seem to capture state at point of definition, so they can't capture their own reference)
private string imageFilename;
private bool exit;

public void CaptureScreenshot(Uri address = null, string imageFilename = null, int msecDelay = 0, bool exit = false)
{
  handler = (s, e) =>
   {
     webBrowser.DocumentCompleted -= handler; //must do first

     this.imageFilename = imageFilename;
     this.exit = exit;

     timerScreenshot.Interval = (msecDelay > 0)? msecDelay : 1;
     timerScreenshot.Enabled = true;
   };

  webBrowser.DocumentCompleted += handler;
  Go(address); //if address == null, will use URL from UI
}

private void timerScreenshot_Tick(object sender, EventArgs e)
{
  timerScreenshot.Enabled = false; //must do first

  BeginInvoke((Action)(() => //Invoke at UI thread
  { //run in UI thread

    BringToFront();
    Bitmap bitmap = webBrowser.GetScreenshot();

    if (imageFilename == null)
      imageFilename = bitmap.ShowSaveFileDialog();

    if (imageFilename != null)
    {
      Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(imageFilename))); //create any parent directories needed
      bitmap.Save(imageFilename);
    }

    bitmap.Dispose(); //release bitmap resources

    if (exit)
      Close(); //this should close the app, since this is the main form

  }), null);
}

您可以在从网站抓取屏幕截图的 WebCapture 工具(http://gallery.clipflair.net/WebCapture,源代码位于:http: //ClipFlair.codeplex.com,参见 Tools/WebCapture 文件夹)中看到上述内容。顺便说一句,如果您想从命令行调用可执行文件,请确保您转到项目的属性并在安全选项卡中关闭 ClickOnce 安全性(否则它无法访问命令行)

于 2013-08-25T15:34:43.837 回答
0

看看背景,看看它是否符合您的需求。

于 2009-10-14T23:15:06.277 回答