C# 如何在不同线程上为列表中的每个元素依次运行函数,并在每个函数运行后更新(被动)视图?

C# 如何在不同线程上为列表中的每个元素依次运行函数,并在每个函数运行后更新(被动)视图?,c#,multithreading,C#,Multithreading,我有一个WinForm和一个«Controller»,其中控制器为列表中的每个元素运行一个长时间运行的函数 我希望: 这些功能应按顺序运行 在循环列表和运行函数时,视图不得冻结 每个功能运行后,应使用运行结果更新视图 到目前为止,(单线程)代码如下所示: View.DateSpan.Workdays.ForEach( d => { var processRunInfo = _processRunner.Run( configFile, d );

我有一个WinForm和一个«Controller»,其中控制器为列表中的每个元素运行一个长时间运行的函数

我希望:

  • 这些功能应按顺序运行
  • 在循环列表和运行函数时,视图不得冻结
  • 每个功能运行后,应使用运行结果更新视图
到目前为止,(单线程)代码如下所示:

View.DateSpan.Workdays.ForEach( 
   d => {
           var processRunInfo = _processRunner.Run( configFile, d );

           UdateViewFrom( processRunInfo );
         } );
上面的代码“起作用”,但会导致视图冻结,因为它使用的是同一个线程,并且会以批处理方式更新视图

Workdays
是一个
IEnumerable
,而
ForEach
做了
List
ForEach
所做的事情,但它是一个扩展方法


\u processRunner.Run
使用提供的参数运行外部命令行应用程序。

我的解决方案是在单独的线程中运行foreach循环,并回调控件(表单)的调用

下面的链接包含了一个不错的例子。
我的解决方案是在一个单独的线程中运行foreach循环,并回调控件(表单)的调用

下面的链接包含了一个不错的例子。

您应该在单独的线程(而不是主GUI线程)中进行长时间运行的计算,以便具有消息循环的主线程可以自由运行。 然后,在for each循环中,您应该使用方法将UpdateViewForm方法封送到GUI线程。您可以选择在被动查看中包装方法,如下所示:

public void DoSomething() {
    if (this.InvokeRequired) {
        this.Invoke(DoSomethingDelegate);
        ...
    }
}
myFormControl1.Invoke(myFormControl1.myDelegate);
或者,不使用标准方法调用,而是使用以下内容:

public void DoSomething() {
    if (this.InvokeRequired) {
        this.Invoke(DoSomethingDelegate);
        ...
    }
}
myFormControl1.Invoke(myFormControl1.myDelegate);

您应该在单独的线程(而不是主GUI线程)中进行长时间运行的计算,以便具有消息循环的主线程可以自由运行。 然后,在for each循环中,您应该使用方法将UpdateViewForm方法封送到GUI线程。您可以选择在被动查看中包装方法,如下所示:

public void DoSomething() {
    if (this.InvokeRequired) {
        this.Invoke(DoSomethingDelegate);
        ...
    }
}
myFormControl1.Invoke(myFormControl1.myDelegate);
或者,不使用标准方法调用,而是使用以下内容:

public void DoSomething() {
    if (this.InvokeRequired) {
        this.Invoke(DoSomethingDelegate);
        ...
    }
}
myFormControl1.Invoke(myFormControl1.myDelegate);

另一个选择是利用PLINQ-并行Linq

你可以这样做

View.DateSpan.Workdays.AsParallel().ForEach( 
   d => {
           var processRunInfo = _processRunner.Run( configFile, d );

           UdateViewFrom( processRunInfo );
         } );

您仍然很可能必须在回调事件中处理返回更新。

另一个选项是利用PLINQ-并行Linq

你可以这样做

View.DateSpan.Workdays.AsParallel().ForEach( 
   d => {
           var processRunInfo = _processRunner.Run( configFile, d );

           UdateViewFrom( processRunInfo );
         } );

您仍然很可能需要在回调事件中处理返回更新。

+1感谢Nenad和Bastiaan为我指出
控件。调用
方向

为了充分利用«被动视图»模式的好处,我不想了解«中WinForms的
控件
类型(该类型只应由视图接口的实现者知道,即从
表单
派生的类型)

下面是我如何解决这个问题的:

  • 控制器使用
    ForEach
    循环一个参数创建一个新的
    Thread
    实例,然后启动创建的实例
之前:

View.DateSpan.Workdays.ForEach(d =>
                                  {
                                     // do stuff...
                                  } );
之后:

new Thread( () => View.DateSpan.Workdays.ForEach( d =>
                                                     {
                                                        // do stuff...
                                                     } ) ).Start();
public string Status
{
  set { ExecuteOnUIThread( _statusLabel, () => _statusLabel.Text = value ); }
}
  • 视图的小部件更新方法使用一个助手方法来检查请求是否来自另一个线程,如果来自另一个线程,则使用
    Invoke
    。请参阅下面的代码
之前:

public string Status
{
  set { _statusLabel.Text = value ); }
}
之后:

new Thread( () => View.DateSpan.Workdays.ForEach( d =>
                                                     {
                                                        // do stuff...
                                                     } ) ).Start();
public string Status
{
  set { ExecuteOnUIThread( _statusLabel, () => _statusLabel.Text = value ); }
}
助手方法:

private static void ExecuteOnUIThread( Control control, Action action )
{
    if ( control.InvokeRequired )
    {
       control.Invoke( action );
    }
    else
    {
       action();
    }
 }

生产WinForm视图工作起来很有魅力,添加了一个while循环,当背景线程在我的BDD故事中工作时旋转线程,我的旧视图«»也是如此。

+1给Nenad和Bastiaan,让他们在
控件中指向我。调用
方向

为了充分利用«被动视图»模式的好处,我不想了解«中WinForms的
控件
类型(该类型只应由视图接口的实现者知道,即从
表单
派生的类型)

下面是我如何解决这个问题的:

  • 控制器使用
    ForEach
    循环一个参数创建一个新的
    Thread
    实例,然后启动创建的实例
之前:

View.DateSpan.Workdays.ForEach(d =>
                                  {
                                     // do stuff...
                                  } );
之后:

new Thread( () => View.DateSpan.Workdays.ForEach( d =>
                                                     {
                                                        // do stuff...
                                                     } ) ).Start();
public string Status
{
  set { ExecuteOnUIThread( _statusLabel, () => _statusLabel.Text = value ); }
}
  • 视图的小部件更新方法使用一个助手方法来检查请求是否来自另一个线程,如果来自另一个线程,则使用
    Invoke
    。请参阅下面的代码
之前:

public string Status
{
  set { _statusLabel.Text = value ); }
}
之后:

new Thread( () => View.DateSpan.Workdays.ForEach( d =>
                                                     {
                                                        // do stuff...
                                                     } ) ).Start();
public string Status
{
  set { ExecuteOnUIThread( _statusLabel, () => _statusLabel.Text = value ); }
}
助手方法:

private static void ExecuteOnUIThread( Control control, Action action )
{
    if ( control.InvokeRequired )
    {
       control.Invoke( action );
    }
    else
    {
       action();
    }
 }

生产WinForm视图工作起来很有魅力,添加了一个while循环,在背景线程在我的BDD故事中工作时旋转线程,我的旧视图«»也是如此。

这对webforms有效吗?我认为它不会找到父窗口句柄,因为它是一个在服务器上运行的进程;«被动视图»是一个WinForm。这对webforms有效吗?我认为它不会找到父窗口句柄,因为它是一个在服务器上运行的进程;«被动视图»是一个WinForm。是的,WinForm(在问题的第一句中陈述)。是的,WinForm(在问题的第一句中陈述)。如问题所述,视图是一个«被动视图»[Fowler],因此无法调用任何东西(它引发一些事件,并实现一些属性和方法)。换句话说,我需要控制器对此负责。您说过被动视图是WinForm。因此,它是从控件继承的,所以它有Invoke方法,可以将执行从任何线程封送到GUI线程。控制器可以设置视图的一个属性,该属性如我之前所示被包装,它将把执行从调用线程“转移”到GUI线程。我在这里假设控制器线程不是GUI线程。如问题中所述,视图是«被动视图»[Fowler],因此无法调用任何东西(它是