为什么稍后调用anotherForm.ShowDialog()会导致System.IProgress.Report()中出现异常?

时间:2017-04-19 10:06:43

标签: c# winforms task-parallel-library

我的表单包含一个文本框txtOutput和以下来源:

public partial class frmMain : Form
{
    private IProgress reporter;

    public frmMain(string[] args)
    {
        InitializeComponent();

        new anotherForm().ShowDialog();
        reporter = new Progress<string>(txtOutput.AppendText);

        Task.Run((Action)ReadReply);
    }

    private void ReadReply()
    {
        reporter.Report("test");
    }
}

如果你运行它,会抛出异常:

System.InvalidOperationException: Cross-thread operation not valid: Control 'txtOutput' accessed from a thread other than the thread it was created on.

   at System.Windows.Forms.Control.get_Handle()
   at System.Windows.Forms.TextBoxBase.GetSelectionStartAndLength(Int32& start, Int32& length)
   at System.Windows.Forms.TextBoxBase.AppendText(String text)
   at System.Progress`1.InvokeHandlers(Object state)
   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

如果你重新排序这两行,一切正常:

reporter = new IProgress<string>(txtOutput.AppendText);
new anotherForm().ShowDialog();
  1. 为什么Progress.Report()会抛出此异常?创建它是为了避免这种例外吗?
  2. 为什么调用ShowDialog()会中断IProgress.Report()

1 个答案:

答案 0 :(得分:1)

如果您使用Progress<T>作为IProgress<T>,预计其行为将如此。

Progress<T>会在SyncronizationContext构建时捕获Report,使用SyncronizedContext时会尝试使用捕获的SyncronizationContext.Current进行调用:
来自docs

  

提供给使用ProgressChanged事件注册的构造函数或事件处理程序的任何处理程序都是通过构造实例时捕获的SynchronizationContext实例调用的。

如果您在ShowDialog之前和之后检查WindowsFormsSynchronizationContext,则会发现它已从SynchronizationContext更改为SyncronizationContext

原来的Progress<T>丢失了。

这就是为什么如果你在ShowDialog之前构建SyncronizationContext

可以通过源代码找到.ShowDialog丢失的原因:

当表单显示为WindowsFormsSynchronizationContext时,会导致为其生成一个新的消息循环,并且只有在以前不存在see here时才会为其安装新的WindowsFormsSynchronizationContext然后在关闭时将其卸载到上一个(here)。

由于在显示第二个表单时上下文已经是{{1}},因此没有创建新的上下文,因此在关闭时导致上下文设置为构造主表单之前的上下文。