【问题标题】:BeginInvoke cause application hang in BackgroundWorkerBeginInvoke 导致应用程序在 BackgroundWorker 中挂起
【发布时间】:2013-08-08 01:14:50
【问题描述】:

我试图解决这个Question 中的问题,但我最终遇到了另一个问题
简而言之,这个问题是在询问如何将一个大文件逐块加载到 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 都会向下滚动到导致应用程序挂起的末尾。

问题是如何停止滚动和应用程序挂起?
在这里我能做些什么来增强字符串连接?

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

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 大小,加载将向下滚动并阻止我继续。

【问题讨论】:

  • 粘贴其余代码,死锁不在这里。
  • 要以这种方式解决它,我宁愿在worker_DoWork 中使用加载文件循环并从那里调用richTextBox1.BeginInvoke 以将另一块文本发送到文本控件。
  • 您在主线程上调用AddText,这违背了BackgroundWorker 的目的。一旦数据在内存中,您需要调用 Dispatcher。我希望你的机器可以处理 1GB+ 的文本字符串。
  • 您使用的是哪个 .NET 版本?
  • @Romoku 我正在使用 .NET4

标签: c# string winforms asynchronous background


【解决方案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 线程变得无响应。

【讨论】:

  • 建议改进:预加载 X 行并在文本框滚动时加载更多。
  • 我认为这也行不通,肯定有很多richTextBox1.AppendText(...)几乎同时运行,这可能会导致richTextBox1无响应
  • @KingKing 是的,它没有,我正在尝试找出是否有办法停止自动向下滚动
  • @Star 您可能想尝试在while 之前调用richTextBox1.SuspendLayout() 并在while 之后调用richTextBox1.ResumeLayout(true)
  • @KingKing 我很肯定现在挂起的原因是向下滚动,我将编辑问题。
【解决方案2】:

只需致电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();
    }
}

【讨论】:

  • 我尝试了两种方式,主题之二导致应用程序挂起,我错过了什么吗??
  • @Star 我不知道,两者都为我工作。我刚刚创建了一个新的 winforms 项目,添加了一个富文本框和一个按钮,并将其添加为按钮的单击处理程序。我给了你第三个选项来尝试我的答案。
  • 我没有,反正我不同意他的观点:)
【解决方案3】:

好吧,这不是最好的解决方案,但它可以满足我的要求,而不是使用 AppendText 会粗暴地向下滚动我使用 +=,但我仍然遇到问题,因此需要 Sleep(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 并将表单自动滚动设置为 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;
            }            
        }

这样我就可以在加载时向下滚动。我希望有人找到更好的解决方案。

【讨论】:

  • 哦,是的,这会减慢reading 的速度,这就是non-responsiveness 被淘汰的原因。您是否尝试过我关于使用SuspendLayoutResumeLayout 的建议?
  • @KingKing 我做了,但我没有发现任何区别。
【解决方案4】:

试试这个:

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 块中并进行处理。

【讨论】:

    【解决方案5】:

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

    【讨论】:

      猜你喜欢
      • 2023-03-19
      • 1970-01-01
      • 2020-10-20
      • 1970-01-01
      • 1970-01-01
      • 2013-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多