BeginInvoke导致应用程序在BackgroundWorker中挂起

时间:2013-08-08 01:14:51

标签: c# string winforms asynchronous background

我试图解决这个Question中的问题,但我最终遇到了另一个问题 简而言之,这个问题是询问如何通过chunk将大文件加载到textBox块中 所以在后台工人Do_work事件中我这样做了:

using (FileStream fs = new FileStream(@"myFilePath.txt", FileMode.Open, FileAccess.Read))
{
    int bufferSize = 50;
    byte[] c = null;        
    while (fs.Length - fs.Position > 0)
    {
        c = new byte[bufferSize];
        fs.Read(c , 0,c.Length);                    
        richTextBox1.AppendText(new string(UnicodeEncoding.ASCII.GetChars(c)));
    }                
}        

由于backgroundWorker无法影响UI元素而无法正常工作,我需要使用BeginInvoke来执行此操作。

所以我改了代码:

delegate void AddTextInvoker();

public void AddText()
{            
    using (FileStream fs = new FileStream(@"myFilePath.txt", FileMode.Open, FileAccess.Read))
    {
        int bufferSize = 50; 
        byte[] c = null;            
        while (fs.Length - fs.Position > 0)
        {
            c = new byte[bufferSize];
            fs.Read(c , 0,c.Length);                    
            richTextBox1.AppendText(new string(UnicodeEncoding.ASCII.GetChars(c)));
        }                
     }           
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    this.BeginInvoke(new AddTextInvoker(AddText));
}

此代码存在两个问题 1-它需要越来越长的时间来附加文本(我认为因为字符串不变性随着时间的推移替换文本会花费更长时间)
2-每增加一次,richTextBox将向下滚动到结尾,导致应用程序挂起。

问题是如何阻止滚动和应用程序挂起?
我该怎么做才能增强字符串连接?

编辑:在经过一些测试并使用Matt的答案后,我得到了这个:

public void AddText()
{            
    using (FileStream fs = new FileStream(@"myFilePath.txt", FileMode.Open, FileAccess.Read))
    {
        int bufferSize = 50; 
        byte[] c = null;             
        while (fs.Length - fs.Position > 0)
        {
           c = new byte[bufferSize];
           fs.Read(c , 0,c.Length);

           string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
           this.BeginInvoke((Action)(() => richTextBox1.AppendText(newText)));

           Thread.Sleep(5000); // here 
         }                
     }        
 }

当加载暂停时我可以无问题地读写,或者挂起,一旦文本超出了richTextBox大小,加载将向下滚动并阻止我继续。

5 个答案:

答案 0 :(得分:1)

我看到的一个问题是你的后台工作人员在后台没有做任何工作。它都在UI线程上运行。这可能就是UI线程没有响应的原因。

我会像这样优化你的DoWork处理程序:

public void AddText()
{            
    using (FileStream fs = new FileStream(@"myFilePath.txt", 
                   FileMode.Open, FileAccess.Read))
    {
        int bufferSize = 50; 
        byte[] c = null;            
        while (fs.Length - fs.Position > 0)
        {
            c = new byte[bufferSize];
            fs.Read(c , 0,c.Length);

            string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
            this.BeginInvoke((Action)(() => richTextBox1.AppendText(newText));
        }                
     }           
}

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    AddText();
}

我所做的是将BeginInvoke的使用本地化到处理程序中进行的单个UI调用。这样,所有其他工作都在后台线程中完成。也许这会有助于UI线程变得无响应。

答案 1 :(得分:1)

只需致电Application.DoEvents即可。这是最简单的事情,无需担心手动创建或同步线程或后台工作者,但您的应用程序仍保持响应。

此外,尝试使用File.ReadLines,这是一个延迟加载的可枚举,而不是手动使用FileStream。例如,这适用于我,并在循环和两行代码中为您提供所需的一切。

private void button1_Click(object sender, EventArgs e)
{
    foreach (var line in File.ReadLines(@"C:\Users\Dax\AppData\Local\Temp\dd_VSMsiLog0D85.txt", Encoding.ASCII))
    {
        richTextBox1.AppendText(line + "\r\n");
        Application.DoEvents();
    }
}

或者,您可以指定块大小并按其加载。这会运行得更快,但是需要更长的时间(不到一秒钟)来读取完整的文件。

private void button1_Click(object sender, EventArgs e)
{
    var text = File.ReadAllText(@"C:\Users\Dax\AppData\Local\Temp\dd_VSMsiLog0D85.txt", Encoding.ASCII);
    const int chunkSize = 1000000;
    for (var i = 0; i < text.Length / chunkSize; ++i)
    {
        richTextBox1.AppendText(text.Substring(chunkSize * i, chunkSize));
        Application.DoEvents();
    }
}

尝试第三个选项,看看你的挂起是由文件还是循环引起的:

void button1_Click(object sender, EventArgs e)
{
    while(true)
    {
        richTextBox1.AppendText("a");
        Application.DoEvents();
    }
}

答案 2 :(得分:0)

好吧,这不是最好的解决方案,但它做了我想要的,而不是使用AppendText,它会向下滚动我使用+ =,我仍然得到挂起所以需要睡眠(100)

public void AddText()
{            
    using (FileStream fs = new FileStream(@"myFilePath.txt", FileMode.Open, FileAccess.Read))
    {
        int bufferSize = 50; 
        byte[] c = null;             
        while (fs.Length - fs.Position > 0)
        {
            c = new byte[bufferSize];
            fs.Read(c , 0,c.Length);

            string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
            this.BeginInvoke((Action)(() => richTextBox1.Text += newText));

            Thread.Sleep(100);
         }                
    }        
}

这实际上不允许我向下滚动直到加载完成, 我无法想出更好的主意。

编辑:在加载完成之前能够读取文本的工作是将richTextBox上的scrollBasrs设置为None并将表单autoscroll设置为true并在TextChanged事件中:

private void richTextBox1_TextChanged(object sender, EventArgs e)
        {
            using (Graphics g = CreateGraphics())
            {
                SizeF size = g.MeasureString(richTextBox1.Text, richTextBox1.Font);
                richTextBox1.Width = (int)Math.Ceiling(size.Width) > 
                    richTextBox1.Width ? (int)Math.Ceiling(size.Width) : richTextBox1.Width;
                richTextBox1.Height = (int)Math.Ceiling(size.Height) >
                    richTextBox1.Height ? (int)Math.Ceiling(size.Height) : richTextBox1.Height;
            }            
        }
这样我就可以在加载时向下滚动。我希望有人找到更好的解决方案。

答案 3 :(得分:0)

试试这个:

private void button1_Click(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    using (FileStream fs = new FileStream(
        @"myFilePath.txt", FileMode.Open, FileAccess.Read))
        {
            int bufferSize = 50;
            byte[] c = null;
            while (fs.Length - fs.Position > 0)
            {
                c = new byte[bufferSize];
                fs.Read(c, 0, c.Length);
                Invoke(new Action(() =>
                    richTextBox1.AppendText(
                        new string(UnicodeEncoding.ASCII.GetChars(c)))));
            }
        }
    }

问题是BeginInvoke,调用AppendText异步,请参阅 http://msdn.microsoft.com/en-us/library/0b1bf3y3.aspxhttp://msdn.microsoft.com/en-us/library/0b1bf3y3.aspxhttp://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx

请记住,Invoke方法可以抛出异常,例如:如果在加载文本时关闭表单,那么将它放在try catch块中并处理它。

答案 4 :(得分:0)

BackgroundWorker可以影响UI线程上的元素,因为它的事件'ProgressChanged'和'RunWorkerCompleted'是在调用线程上执行的。以下示例在单独的线程上将整数变量增加到10,并将每个增量传递回UI线程。

BackgroundWorker _worker;

private void button1_Click(object sender, EventArgs e)
{
    _worker.RunWorkerAsync();
}

private void Form1_Load(object sender, EventArgs e)
{
    _worker = new BackgroundWorker();
    _worker.WorkerReportsProgress = true;
    _worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    _worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = (BackgroundWorker)sender;

    for (int i = 0; i < 10; i++)
    {
        // pass value in parameter userState (2nd parameter), since it can hold objects
        worker.ReportProgress(0, i); // calls ProgressChanged on main thread
    }
}

void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // get value, passed in DoWork, back from UserState
    richTextBox1.AppendText(e.UserState.ToString());
}