带循环的C#乘法表程序不断冻结,两个事件无法正常工作

时间:2015-11-13 05:10:58

标签: c# loops

我一直在使用C#/ Visual Studio中的这个简单的乘法表程序,这给我带来了麻烦,因为循环(特别是do-while)对我来说非常困难。我应该使用嵌套的for循环和嵌套的do-while循环,虽然我没有收到任何错误,但是当我调试或运行程序并单击 while循环按钮时,它会冻结并需要要停下来; for-loop 按钮在点击时仅显示“0”,但执行循环按钮似乎正常工作。

我相信这些是有问题的部分 - for loop:

for i in range(100):

while-loop:

  for (r = 1; r < 10; r++)
        {
            for (c = 1; c < 10; c++)
            {
                intResult = r * c;
                if (intResult < 10)
                    strSpace = "  ";  //two spaces 
                else
                    strSpace = " ";   //one space
                txtTable.AppendText(strSpace); // insert space
                txtTable.AppendText(intResult.ToString());  //insert result

                txtTable.AppendText("\r\n");  //Move down one line
            }
        }
    }

完整的参考代码:

  private void btnWhileLoop_Click(object sender, EventArgs e)
    {
        int r = 0; //row
        int c = 0; //column
        int intResult;
        string strSpace;

        txtTable.Clear();    //clear the text box
        txtTable.Refresh();  //refresh the form before exiting the method
        Thread.Sleep(1000);  //wait one second to see the clear text box

        //Outer loop goes down the rows
         r = 1; //initialize r
        do
        {

           //Inner loop goes across the columns
            c = 1; //initialize c
            do
            {


               intResult = r * c;
            } while (r < 10);

           } while (c < 10);
                strSpace = "  ";

                txtTable.AppendText(strSpace); // insert space

               txtTable.AppendText(intResult.ToString());  //insert result
                c++;  //increment c
        {
            txtTable.AppendText("\r\n");  //Move down one line
            r++;  //increment r
        }
    }

谢谢。

1 个答案:

答案 0 :(得分:2)

你有两个不同的问题,但两者都是由同一个根本错误引起的:你在处理事情时不会让UI线程更新。

顺便说一句:您的代码非常混乱。您的活动处理程序的名称似乎与他们实际所做的不相符。例如,do / while示例位于名为btnWhileLoop_Click()的方法中,而嵌套for循环示例位于名为btnDoWhileLoop_Click()的方法中。然后你有一个名为btnForLoop_Click()的方法,根本没有任何循环。出于本答案的目的,我将至少忽略方法名称,因为它们与特定实现相关,并且仅用于识别每种方法。

总之...

do / while示例中,主要问题是您没有更改控制循环的变量。因为它永远不会改变,所以允许循环终止的表达式永远不会计算为false,因此循环只会永远运行。

在同样的方法中,你有一堆代码,看起来你最初可能已经正确地编写了它,然后你的小兄弟出现并将你的程序从桌面上敲下来,这样当你选择备份时,你只需按随机顺序将它们设置回方法即可。即似乎所有正确的位都存在,但它们不是任何连贯的顺序。

这是一个应该更好的方法版本:

    private void btnWhileLoop_Click(object sender, EventArgs e)
    {
        int r = 0; //row
        int c = 0; //column
        int intResult;
        string strSpace;

        txtTable.Clear();    //clear the text box
        txtTable.Refresh();  //refresh the form before exiting the method
        Thread.Sleep(1000);  //wait one second to see the clear text box

        //Outer loop goes down the rows
         r = 1; //initialize r
        do
        {
            //Inner loop goes across the columns
            c = 1; //initialize c
            do
            {
                intResult = r * c;
                c++;  //increment c
                txtTable.AppendText(" "); // insert space
                txtTable.AppendText(intResult.ToString());  //insert result
            } while (c < 10);

            txtTable.AppendText("\r\n");  //Move down one line
            r++;  //increment r
        } while (r < 10);
    }

上面的问题仍然是在整个过程完成之前屏幕上没有任何变化。但至少现在可以完成整个事情。 :)我将使用嵌套的for循环示例来说明如何解决第二个问题,即在整个过程完成之前屏幕上没有显示任何内容。

有多种方法可以解决这个问题,但恕我直言最常用和自然的方法是在后台任务中运行你的处理,并让UI线程同时工作。这可能看起来像这样:

    private async void btnDoWhileLoop_Click(object sender, EventArgs e)
    {
        int r = 0; //row
        int c = 0; //column
        int intResult;
        string strSpace;

        txtTable.Clear();       //clear the text box
        await Task.Delay(1000); //wait one second to see the clear text box

        txtTable.AppendText("\r\n");

        // set up a helper to make it easy to update the UI from the background
        // task Non-standard event arg type of "string", because we don't need
        // anything fancier
        Progress<string> progress = new Progress<string>();

        // This event is raised when progress.Report() is called (see below)
        progress.ProgressChanged += (sender, e) =>
        {
            txtTable.AppendText(e);
        };

        // Wrap the loops in an anonymous method (i.e. the () => { ... } syntax)
        // and pass that to Task.Run() so it can run as a background task
        await Task.Run(() =>
        {
        //Outer loop goes down the rows
        //initialize r
        //do

        //Inner loop goes across the columns
        //initialize c
        //do
        for (r = 1; r < 10; r++)
        {
            for (c = 1; c < 10; c++)
            {
                intResult = r * c;
                if (intResult < 10)
                    strSpace = "  ";  //two spaces 
                else
                    strSpace = " ";   //one space

                // While the task is running in a thread other than your original
                // UI thread, using the "progress" object here allows the task to pass
                // data back to the UI thread where it is allowed to call e.g.
                // txtTable.AppendText() to update the UI.
                progress.Report(strSpace); // insert space
                progress.Report(intResult.ToString());  //insert result

            }

            // I think you wanted this line outside the inner loop
            progress.Report("\r\n");  //Move down one line
        }
        });

        // There's nothing else to do. But if you wanted to you could add code here
        // to do something after the task is run. Using "await" allows the UI thread
        // to keep running while the task is running, and then return control to
        // this method here when it's done.
    }

备注:

  • 您可能不熟悉第二个示例中的任务,线程,进度,异步等内容。不幸的是,虽然您实际上只是想了解循环,但您已选择在GUI API(即Winforms)中实现您的代码。这些API通常对如何更新UI有重要且严格的规则,对于您正在编写的代码类型,遵守这些规则的最佳方法是编写如上所述的代码。恕我直言,如果您在控制台程序中实现循环示例,那么输出就会立即变得更好,更容易。我希望以上内容不会使你太迷惑。
  • async关键字用于标记使用await关键字的方法。您可以在其他地方阅读有关如何使用这些内容的详简短版本是使用await允许该方法实际返回到调用者临时,以便调用该方法的线程可以继续运行,而方法本身正在等待某些事情发生。当发生这种情况时,该方法将在await语句或表达式之后继续执行。这是一种关键技术,允许UI线程继续工作,而其他任务在另一个线程中运行。
  • 在大多数GUI API中,包括Winforms,WPF,Winrt,Java等,都有一个单独的&#34; UI线程&#34;必须访问UI对象的位置。如果您编写的代码在某个其他线程中执行,并且该代码需要以某种方式与UI交互或更新UI,则可以使用特定于API的技术来实现该功能。在.NET中,执行此操作的最简单机制之一是使用Progress<T>类。这个类隐藏了所需的所有血腥细节(参见例如Control.Invoke()方法),并且可以很容易地以直接的方式将数据传递回UI线程。
  • 以上所有内容的净效果是在运行循环时不会阻止UI线程。相反,他们可以在不同的线程中运行。这样,当他们访问UI(通过progress对象)时,UI可以通过重绘自身并在任务仍在运行时在屏幕上显示新数据来响应您的更新。
  • 所有这一切都会很快发生。您可能希望将Thread.Sleep(100)语句抛出到内部循环中,以便在实际进行时看到事情发生。只有100次迭代,完全有可能没有它,即使UI线程未被阻止,仍然不会更新,直到处理结束。
  • 您可以将第二个示例中的完全相同的技术应用于第一个示例(do / while示例),以便在循环实际运行时也可以更新UI。 / LI>