朋友托我问个问题,这个C#代码,为什么会导致winForm界面卡死
他写了一个用于持续写入log的工具类。用到异步锁,目的是保证上次异步写完,后面再写,否则前面没写入,后面又调用要写入,.NET就会抛出IOException了public class LogAsyncWriter
{
private readonly SemaphoreSlim _Semaphore = new SemaphoreSlim(1, 1);
private readonly StreamWriter _StreamWriter;
public LogAsyncWriter(string filePath)
{
_StreamWriter = new StreamWriter(filePath, true, Encoding.UTF8);
}
public LogAsyncWriter(string filePath, Encoding encoding)
{
_StreamWriter = new StreamWriter(filePath, true, encoding);
}
public async Task WriteLogAsync(string logText, bool isAppendLine = true)
{
await _Semaphore.WaitAsync();
try
{
if (isAppendLine == true)
await _StreamWriter.WriteLineAsync(logText);
else
await _StreamWriter.WriteAsync(logText);
}
finally
{
_Semaphore.Release();
}
}
public async Task CloseAsync()
{
await _Semaphore.WaitAsync();
try
{
_StreamWriter.Flush();
_StreamWriter.Close();
}
finally
{
_Semaphore.Release();
}
}
}
然后他又写了一个测试代码,在Form中点击一个按钮的事件
private LogAsyncWriter _LogAsyncWriter;
private string _OnceWriteText;
private void BtnTestLogAsyncWriter_Click(object sender, EventArgs e)
{
_LogAsyncWriter = new LogAsyncWriter(@"D:\TestLogAsyncWriter.txt");
StringBuilder lotsOfTextSB = new StringBuilder();
for (int i = 0; i < 2048; i++)
lotsOfTextSB.Append("a");
_OnceWriteText = lotsOfTextSB.ToString();
UIUtil.AppendTextToRichTextBox(RtxConsole, $"[{DateTime.Now.ToString("HH:mm:ss")}]准备写入Log");
_StartWriteLog();
// 做其他事情
UIUtil.AppendTextToRichTextBox(RtxConsole, $"[{DateTime.Now.ToString("HH:mm:ss")}]运行到后续代码");
}
private async void _StartWriteLog()
{
// 连续3秒写入大量内容
DateTime endTime = DateTime.Now.AddSeconds(3);
int writeCount = 0;
while (DateTime.Now < endTime)
{
await _LogAsyncWriter.WriteLogAsync(_OnceWriteText);
writeCount++;
}
AppendTextToRichTextBox(RtxConsole, $"[{DateTime.Now.ToString("HH:mm:ss")}]完成3秒的写入,共写入{writeCount}次");
await _LogAsyncWriter.CloseAsync();
AppendTextToRichTextBox(RtxConsole, $"[{DateTime.Now.ToString("HH:mm:ss")}]完成文件关闭");
}
public static void AppendTextToRichTextBox(RichTextBox rtx, string text, bool isAppendNewLine = true)
{
// 让富文本框获取焦点
rtx.Focus();
// 设置光标的位置到文本结尾
rtx.Select(rtx.TextLength, 0);
// 滚动到富文本框光标处
rtx.ScrollToCaret();
// 追加内容
rtx.AppendText(text);
// 换行
if (isAppendNewLine == true)
rtx.AppendText(Environment.NewLine);
// 刷新UI
rtx.Refresh();
}
但是发现,在点击按钮,3秒持续写入过程中,整个form界面都是卡死的,他不明白这是为什么。以下是它提问的原话:
_StartWriteLog是异步方法,我在button点击事件中,并没有await 这个函数,而运行看,也确实在开始时候,就已经打印出:“运行到后续代码”,说明确实没有阻塞。但是winfrom界面为什么会卡呢? 让我们来看一下AI的回答
这可能是由于在 WinForms 中,所有 UI 更新都需要在 UI 线程上进行。在你的 _StartWriteLog 方法中,你在执行写入操作的同时,也在更新 UI(将写入计数和完成时间添加到 RichTextBox)。虽然你的写入操作是异步的,但 UI 更新是在 UI 线程上进行的,所以在写入操作完成之前,UI 线程可能会被阻塞。
你可以尝试使用 Task.Run 来将 _StartWriteLog 的内容放在一个新的线程中运行,这样 UI 线程就不会被阻塞。然后,你可以使用 Control.Invoke 或 Control.BeginInvoke 来在 UI 线程上更新 UI。以下是一个示例:
private void BtnTestLogAsyncWriter_Click(object sender, EventArgs e)
{
_LogAsyncWriter = new LogAsyncWriter(@"D:\TestLogAsyncWriter.txt");
StringBuilder lotsOfTextSB = new StringBuilder();
for (int i = 0; i < 2048; i++)
lotsOfTextSB.Append("a");
_OnceWriteText = lotsOfTextSB.ToString();
UIUtil.AppendTextToRichTextBox(RtxConsole, $"[{DateTime.Now.ToString("HH:mm:ss")}]准备写入Log");
Task.Run(() => _StartWriteLog());
// 做其他事情
UIUtil.AppendTextToRichTextBox(RtxConsole, $"[{DateTime.Now.ToString("HH:mm:ss")}]运行到后续代码");
}
private async void _StartWriteLog()
{
// 连续3秒写入大量内容
DateTime endTime = DateTime.Now.AddSeconds(3);
int writeCount = 0;
while (DateTime.Now < endTime)
{
await _LogAsyncWriter.WriteLogAsync(_OnceWriteText);
writeCount++;
}
this.Invoke((Action)(() =>
{
AppendTextToRichTextBox(RtxConsole, $"[{DateTime.Now.ToString("HH:mm:ss")}]完成3秒的写入,共写入{writeCount}次");
}));
await _LogAsyncWriter.CloseAsync();
this.Invoke((Action)(() =>
{
AppendTextToRichTextBox(RtxConsole, $"[{DateTime.Now.ToString("HH:mm:ss")}]完成文件关闭");
}));
}
在这个修改后的代码中, _StartWriteLog 的内容在一个新的线程中运行,而更新 UI 的代码在 UI 线程中运行,因此 UI 不会在写入操作进行时被阻塞。 zbr878458173 发表于 2023-5-18 08:17
让我们来看一下AI的回答
这可能是由于在 WinForms 中,所有 UI 更新都需要在 UI 线程上进行。在你的 _Star ...
这真是ai干的 此类问题丢给GPT,能快速解决 我认为是while循环放在UI线程导致的, 把while循环放在一个单独的线程应该可以解决问题, while等待了三秒, 也就是阻塞了主线程3秒, 虽然内部用了await, 但是await的执行时间很短, 所以和死循环等同, 导致主线程卡顿3秒
while (DateTime.Now < endTime)
{
await _LogAsyncWriter.WriteLogAsync(_OnceWriteText);
writeCount++;
} 我记得如果 你要 在 其他 线程 更新 UI元素的话 要注意两点。
1.是耗时的任务 用异步或线程处理。
2.更新页面UI 要用委托或回调的形式 让 UI主线程 执行。
不然 就有可能出现 界面假死 直到 任务完成。 非UI线程更新UI,请使用begininvoke或者invoke 本帖最后由 a952135763 于 2023-5-17 22:36 编辑
private void BtnTestLogAsyncWriter_Click(object sender, EventArgs e)
这句代码改成
private async void BtnTestLogAsyncWriter_Click(object sender, EventArgs e)
就行了{:1_918:}
调用_StartWriteLog的时候 使用await
对了
private async void _StartWriteLog()
的void改成 task
原因就是 async void 和 async task的行为问题
async void在事件程序中并不会切换上下文,会让函数在调用方的上下文运行.
所以你调用方是主线程,当主线程的事件循环完成了一次 接下来就要运行你的_StartWriteLog方法
_StartWriteLog是堵塞的 界面当然卡死了 while 循环中加上 : Application.DoEvents(); 也可以防止界面假死
while (DateTime.Now < endTime)
{
await _LogAsyncWriter.WriteLogAsync(_OnceWriteText);
writeCount++;
Application.DoEvents(); //让界面响应事件
} a952135763 发表于 2023-5-18 09:25
private void BtnTestLogAsyncWriter_Click(object sender, EventArgs e)
但按你说这个该,照样卡住的呀
页:
[1]
2