【问题标题】:I need to stop execution during recursive algorithm我需要在递归算法期间停止执行
【发布时间】:2010-03-27 13:51:43
【问题描述】:

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

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

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

  • (暂停/恢复):我听说使用它是个坏主意。

  • (Waihandles):仍在阅读有关它们的信息。

  • (监控等待/恢复)。

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

【问题讨论】:

  • 如果你不想冻结 UI 线程,那么不要在 UI 线程上运行你想冻结的代码。 (医生,我这样做会很痛。--所以不要那样做。)在它自己的线程上运行它,然后冻结它。

标签: c# multithreading recursion


【解决方案1】:

如果您想在“后台”进行工作,同时您的用户界面仍然是交互式的,或者如果您想拆分慢速任务以便它可以在多个处理器内核上执行,您只需要使用线程。

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

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

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

【讨论】:

  • 由于我的搜索,我发现线程是正确的选择,因为我需要我的 GUI 保持交互以让用户进入下一步,如果您有任何其他建议,请告诉我。跨度>
【解决方案2】:

如果您对递归教学感兴趣,同时允许算法在每次迭代时暂停,您可能需要考虑创建一个状态管理器类来包装算法并公开显式堆栈。递归算法实际上是基于堆栈的算法的一种形式,因此能够运行单次迭代并查看堆栈内容将有助于演示递归的实际运行方式。我可以想象这样的事情:

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 并以您想要的任何速度进行迭代。每次迭代后,您可以检查堆栈并显示它,以提供有关算法进展情况的有用信息。

【讨论】:

  • 好主意,最恰当地回答问题中的细节,imo。对我来说,这个问题是关于“一次为课程作业做递归的事情”,而不是“在后台做任何事情”。 +1
【解决方案3】:

从根本上说,这个问题实际上与递归或多线程无关,只是这样:

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

实现您自己的线程模型不是的方法,尤其是当您刚刚开始学习多线程/异步操作时。 .NET Framework 已经具有一个组件来满足您的需求:BackgroundWorker,它适用于 Winforms 和 WPF(以及几乎任何其他架构)。 p>

使用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 事件,并确保在BackgroundWorker 上将WorkerSupportsCancellation 设置为true。之后,您运行该操作:

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】:

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

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

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

    【讨论】:

    • 当我尝试不在主线程中的(睡眠/中断)时,它在一个单独的线程中,但 UI 冻结了。感谢您的新方法,我会考虑的。
    【解决方案5】:

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

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

    This example 可能会有所帮助。

    【讨论】:

      【解决方案6】:

      使用等待句柄,您正朝着正确的方向前进。

      尝试使用AutoResetEvent。我认为这是将您带到您想去的地方的一种方法。

      【讨论】:

        【解决方案7】:

        您实际上并不需要多线程。递归或迭代算法已经很适合单步执行。

        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();
                };
            }
        }
        

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

        【讨论】:

          【解决方案8】:

          AutoResetEvent + BackgroundWorker 是完美的选择 :)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-11-18
            • 2019-09-03
            • 1970-01-01
            • 1970-01-01
            • 2021-01-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多