2010-03-27 110 views
2

期间停止执行我有一个问题,在选择实现我的目标正确的方法。 我对算法教学系统的工作,我使用C#。我需要将我的算法分成几步,每一步都会包含一个递归。 我必须停止在每个步骤之后执行,用户可以在我的GUI用一个按钮然后移动到下一个步骤(下一递归)。我需要递归算法

搜索,线程后是正确的选择,但我发现了几个方法:

  • (的Thread.sleep /中断):没有工作,我的GUI冻结!

  • (挂起/恢复):我读过这是一个坏主意,使用。

  • (Waithandles):仍然阅读有关它们。

  • (显示器等待/恢复)。

我没有太多的时间去尝试和读取所有以前的方法,请帮我在选择适合我的system.Any建议是极值欢迎的最佳方法。

+1

如果你不想冻结UI线程,然后不要运行要冻结UI线程上的代码。 (医生,当我这样做的时候会感到痛苦 - 所以*不要这样做*)。运行它自己的线程并冻结它。 – 2010-03-27 15:04:10

回答

3

从根本上说,这个问题是不是真正的递归或支持多线程,它很简单:

如何在一个GUI应用程序的后台执行一个长期运行的操作,这样该应用住宿响应?

实现自己的线程模型是去这里的路,特别是如果你是刚开始学习多线程/异步操作。 .NET框架已经你想要做的一个组件:BackgroundWorker,它可以在Winforms和WPF(以及几乎任何其他体系结构)中工作。

使用BackgroundWorker来完成您想要的任务非常非常容易。我将假设Winforms为这个例子,但是这是just as easy in WPF

// Don't actually write this line; it will be in the .designer.cs file when you 
// drop a BackgroundWorker onto the form/control. This is for reference only. 
private BackgroundWorker bwRecursive; 

private void bwRecursive_DoWork(object sender, DoWorkEventArgs e) 
{ 
    MyTreeNode root = (MyTreeNode)e.Argument; 
    ExecuteRecursiveOperation(root); 
} 

private void bwRecursive_RunWorkerCompleted(object sender, 
    RunWorkerCompletedEventArgs e) 
{ 
    // Optionally update the GUI with the results here 
} 

private void ExecuteRecursiveOperation(MyTreeNode node) 
{ 
    if (bwRecursive.CancellationPending) 
     return; 

    foreach (MyTreeNode childNode in node.ChildNodes) 
    { 
     if (bwRecursive.CancellationPending) 
      break; 

     ExecuteRecursiveOperation(childNode); 
    } 
} 

显然,你也必须要连接的DoWorkRunWorkerCompleted事件,并确保设置WorkerSupportsCancellationtrueBackgroundWorker。在此之后,运行与操作:

bwRecursive.RunWorkerAsync(someTreeNode); 

并与取消:

bwRecursive.CancelAsync(); 

这里唯一的皱纹是,你说你希望每个之后暂停(不停止)执行“步”。我可能会使用AutoResetEvent来做到这一点,这是一种事件类型,每次等待成功时都会重置其信号(“就绪”)状态。再次,这是只有几行代码整合:

public class MyForm : Form 
{ 
    private AutoResetEvent continueEvent = new AutoResetEvent(false); 

    // Previous BackgroundWorker code would go here 

    private void ExecuteRecursiveOperation(MyTreeNode node) 
    { 
     if (bwRecursive.CancellationPending) 
      return; 

     foreach (MyTreeNode childNode in node.ChildNodes) 
     { 
      continueEvent.WaitOne(); // <--- This is the new code 

      if (bwRecursive.CancellationPending) 
       break; 

      ExecuteRecursiveOperation(childNode); 
     } 
    } 

    private void btnContinue_Click(object sender, EventArgs e) 
    { 
     continueEvent.Set(); 
    } 

    private void btnCancel_Click(object sender, EventArgs e) 
    { 
     bwRecursive.CancelAsync(); 
     continueEvent.Set(); 
    } 

    private void btnStart_Click(object sender, EventArgs e) 
    { 
     continueEvent.Set(); 
     bwRecursive.RunWorkerAsync(...); 
    } 
} 

有一件事可能在这里需要额外的解释,那就是取消方法,它先取消,然后设置continueEvent。有必要这样做,因为如果工作人员仍在等待事件发生,实际上将不会取消到取消阶段,因此当您取消时,您需要允许该工作人员继续。如果您希望执行第一步而不要求用户点击“继续”,则您还需要设置continueEvent开始工作人员。“

4

你只需要,如果你想“在后台”,而你的用户界面仍然是互动的,或者如果你想达到分裂缓慢的任务,以便它可以在多个处理器内核来执行办工作中使用线程。

你描述听起来像你只需要编写一个单独的线程:

  • 记住当用户要由顺序排列。
  • 每次点击您的按钮,执行下一步

如果你想使用线程,那么你可以使用的AutoResetEvent,让你的线程遍历的步骤,对WaitOne的每个迭代。只要用户点击按钮开始下一步,用户界面就会发出该事件的信号。 (您还需要在一步完成,因此用户只能运行在序列中的步骤通知UI - 以为你可以只设立button.Enabled =假,当他们开始了一步,而线程可以使用button.Invoke设置当完成步骤时,Enabled = true)。但是,线程声音过于复杂,你似乎在描述。

+0

我发现线程是正确的选择,由于我的搜索,因为我需要我的GUI保持互动,让用户移动到下一步,如果您有任何其他建议,请让我知道。 – Lisa 2010-03-27 14:09:57

1

一切你上市将阻止执行,如果你的代码是在主线程中执行将冻结UI。

如果您只需要暂停操作,然后恢复或取消操作,那么您必须将计算移至单独的线程,以便冻结它不会影响用户界面。

您也可以从不同的角度来处理你的问题。你可以重新设计你的算法,使他们可以从任何位置重新开始 - 让你的递归堆栈外,它作为一个参数传递

+0

当我尝试(睡眠/中断)不在主线程,它是在一个单独的线程,但UI冻结。感谢新方法,我会考虑它。 – Lisa 2010-03-27 14:22:52

0

线程是很好的,只要你使用它的正确方法。我要说开始与以下步骤:

  1. 创建,还有一种形式,两个按钮和两个ProgressBar S(B1,B2,P1,P2 ...例如)。
  2. 当按钮b1被点击时,在BackgroundWorker中启动递归算法1,并显示进度条p1中的进度。如果重要的是让用户不要按其他按钮,则可以禁用按钮,直到p1完成。
  3. 当用户点击p2中的按钮b1,update the progress bar时。

This example可能会有帮助。

0

你正朝着正确的方向等待处理。

尝试使用AutoResetEvent。我认为这是让你想去的地方。

4

如果您对递归教学感兴趣,同时允许算法在每次迭代时暂停,您可能需要考虑创建一个状态管理器类来包装算法并公开一个显式堆栈。递归算法实际上是一个。的基于堆栈的算法形式,所以不必跑单次迭代,看到堆栈中的内容将帮助演示如何递归实际运行的能力,我可以想像这样的:

public struct AlgorithmParameters 
{ 
    public readonly int Parameter1; 
    public readonly int Parameter2; 

    public AlgorithmParameters(int parameter1, int parameter2) 
    { 
     Parameter1 = parameter1; 
     Parameter2 = parameter2; 
    } 
} 

public class RecursiveAlgorithm 
{ 
    private Stack<AlgorithmParameters> _parameterStack = new Stack<AlgorithmParameters>(); 

    public IEnumerable<AlgorithmParameters> ParameterStack 
    { 
     get { return _parameterStack; } 
    } 

    public IEnumerable<RecursiveAlgorithm> RunAlgorithm(int parameter1, int parameter2) 
    { 
     return RunAlgorithm(new AlgorithmParameters(parameter1, parameter2)); 
    } 

    public IEnumerable<RecursiveAlgorithm> RunAlgorithm(AlgorithmParameters parameters) 
    { 
     //Push parameters onto the stack 
     _parameterStack.Push(parameters); 

     //Return the current state of the algorithm before we start running 
     yield return this; 

     //Now execute the algorithm and return subsequent states 
     foreach (var state in Execute()) 
      yield return state; 
    } 

    private IEnumerable<RecursiveAlgorithm> Execute() 
    { 
     //Get the parameters for this recursive call 
     var parameters = _parameterStack.Pop(); 

     //Some algorithm implementation here... 

     //If the algorithm calls itself, do this: 
     int newParameter1 = 2; //Parameters determined above... 
     int newParameter2 = 5; 
     foreach (var state in RunAlgorithm(newParameter1, newParameter2)) 
      yield return state; 

     //More implementation here... 

     //We've finished one recursive call, so return the current state 
     yield return this; 
    } 
} 

我没有测试过这个代码,但希望这给你的想法。“yield return”结构本质上将它变成一个实现递归算法的状态机,其中stack re在每次迭代中呈现参数。为了迭代运行算法,获得一个IEnumerator并以任何你喜欢的速度迭代。每次迭代后,您可以检查堆栈并显示它以提供有关算法如何进展的有用信息。

+0

伟大的想法和对问题中的细节最合适的回答,imo。对我来说,这个问题是关于“一步一步做递归的事情”,而不是“在背景中做任何事情”。 +1 – 2010-03-27 15:27:35

0

你不需要为此多线程。递归或迭代算法已经适用于步进。

class MainWnd : Form{ 
    /* 
    * A typical recursive factorial function would be 
    * implemented something like this: 
    */ 
    double Factorial(int n){ 
     return (double)n * Factorial(n - 1); 
    } 

    /* 
    * Alternatively, factorial can be implemented iteratively: 
    */ 
    double Factorial(int n){ 
     double product = n; 
     while(--n > 1) 
      product *= n; 
     return product; 
    } 

    /* 
    * Let's break the logic up into steps, so that it 
    * saves its state between steps and resumes where 
    * it left off for the next step. 
    */ 

    int factorialN; 
    double factorialProduct; 

    void BeginSteppedFactorial(int n){ 
     factorialProduct = n; 
     factorialN  = n - 1; 
    } 
    bool StepFactorial(){ 
     if(factorialN <= 1) 
      return true; // we're done, the result is ready 

     factorialProduct *= factorialN--; 
     return false; // we're not yet done, call the next step 
    } 
    double GetSteppedFactorialResult(){return factorialProduct;} 


    static void Main(){Application.Run(new MainWnd());} 
    MainWnd(){ 
     BeginSteppedFactorial(32); 

     Label lbl = new Label(){ 
      AutoSize = true, 
      Location = new Point(10, 10), 
      Text  = GetSteppedFactorialResult().ToString(), 
      Parent = this 
     }; 
     Button btn = new Button(){ 
      Location = new Point(10, 30), 
      Text  = "Next Step", 
      Parent = this 
     }; 
     btn.Click += (s, e)=>{ 
      if(StepFactorial()){ 
       btn.Text = "Finished"; 
       btn.Enabled = false; 
      } 
      lbl.Text = GetSteppedFactorialResult().ToString(); 
     }; 
    } 
} 

这是一个工作的例子,尽管阶乘是一个很简单的功能,骨骼是在这里更复杂的程序。

0

的AutoResetEvent + BackgroundWorker的是一个完美的选择:)