我需要在递归算法期间停止执行

时间:2010-03-27 13:51:43

标签: c# multithreading recursion

我在选择正确的方法来实现目标时遇到了问题。 我正在研究算法教学系统,我正在使用C#。我需要将我的算法分成几步,每一步都包含一个递归。 我必须在每一步之后停止执行,然后用户可以使用我的GUI中的按钮移动到下一步(下一次递归)。

搜索之后,线程是正确的选择,但我找到了几种方法:

  • (Thread.sleep / interrupt):没用,我的GUI冻结了!!

  • (暂停/恢复):我读过这是一个坏主意。

  • (Waithandles):还在读它们。

  • (监控等待/恢复)。

我没有太多时间尝试阅读以前的所有方法,请帮助我选择适合我系统的最佳方法。任何建议都是极为欢迎的。

8 个答案:

答案 0 :(得分:4)

如果您希望在用户界面继续保持交互的同时“在后台”工作,或者您希望将慢任务拆分以便可以在多个处理器核心上执行,则只需使用线程。

您所描述的内容听起来只需要编写一个帖子:

  • 记住用户在序列中的位置。
  • 每次点击按钮,执行下一步

如果您希望使用线程,那么您可以使用AutoResetEvent来允许线程循环执行这些步骤,并在每次迭代时使用WaitOne。只要用户单击按钮开始下一步,您的用户界面就会发出该事件的信号。 (您还需要在步骤完成时通知UI,以便用户只能按顺序运行步骤 - 认为您可以在启动步骤时设置button.Enabled = false,并且线程可以使用button.Invoke来设置完成步骤后,Enabled = true)。但是线程听起来过于复杂,因为你似乎在描述它。

答案 1 :(得分: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”构造将基本上将其转换为实现递归算法的状态机,其中堆栈表示每次迭代时的参数。要迭代运行算法,请获取IEnumerator并以您想要的任何速度迭代。在每次迭代之后,您可以检查堆栈并显示它,以提供有关算法进展情况的有用信息。

答案 2 :(得分:3)

从根本上说,这个问题并不是关于递归或多线程的,只是这个:

  

如何在GUI应用程序的后台执行长时间运行的操作,以便应用程序保持响应?

实现自己的线程模型是这里的方法,特别是如果您刚刚开始学习多线程/异步操作。 .NET Framework已经拥有您想要做的事情的组件: 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事件联系起来,并确保在WorkerSupportsCancellation上将true设置为BackgroundWorker。之后,使用以下命令运行操作:

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

答案 3 :(得分:1)

您列出的所有内容都会阻止执行,如果您的代码在主线程中执行,则会冻结UI。

如果您只需要暂停操作然后恢复或取消 - 那么您必须将计算移动到一个单独的线程中,以便冻结它不会影响UI。

您也可以从不同的角度处理问题。您可以重新设计算法,以便可以从任何点重新启动它们 - 使您的递归堆栈在外部并将其作为参数传递

答案 4 :(得分:0)

只要您以正确的方式使用它,线程就会很好。我会说从以下步骤开始:

  1. 使用两个按钮和两个ProgressBar创建一个表单(例如b1,b2,p1,p2 ......)。
  2. 点击按钮b1后,在BackgroundWorker中启动递归algo1,并在进度条p1中显示进度。如果让您的用户不要按其他按钮很重要,那么您可以禁用按钮,直到p1完成。
  3. 当用户点击按钮b1,p2。{/ li>中的update the progress bar

    This example可能会有所帮助。

答案 5 :(得分:0)

你正朝着正确的方向前进。等等。

尝试使用AutoResetEvent。我认为这是让你到达目的地的一种方式。

答案 6 :(得分: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();
        };
    }
}

这是一个有效的例子,虽然阶乘是一个非常简单的函数,但是骨架在这里用于更复杂的例程。

答案 7 :(得分:0)

AutoResetEvent + BackgroundWorker是完美的选择:)