BeginInvoke导致应用程序挂起在BackgroundWorker中
本文关键字:BackgroundWorker 挂起 应用程序 BeginInvoke | 更新日期: 2023-09-27 18:26:45
我试图解决这个问题中的问题,但最终遇到了另一个问题
简而言之,这个问题是问如何将一个巨大的文件一块一块地加载到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大小,加载将向下滚动,并阻止我继续。
我看到的一个问题是,你的后台工作人员没有在后台做任何工作。这一切都在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线程变得无响应。
只需调用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();
}
}
好的,这不是最好的解决方案,但它可以满足我的需求,而不是使用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;
}
}
这样我就可以在加载时向下滚动。我希望有人能找到更好的解决方案。
试试这个:
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.aspx和http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
请记住,Invoke方法可能会引发异常,例如:如果在加载文本时关闭表单,则将其放入try-catch块中并进行处理。
BackgroundWorker可以影响UI线程上的元素,因为它的事件"ProgressChanged"answers"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());
}