吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1657|回复: 14
收起左侧

[求助] 朋友托我问个问题,这个C#代码,为什么会导致winForm界面卡死

[复制链接]
ilovecomputer66 发表于 2023-5-18 08:03
他写了一个用于持续写入log的工具类。用到异步锁,目的是保证上次异步写完,后面再写,否则前面没写入,后面又调用要写入,.NET就会抛出IOException了
[C#] 纯文本查看 复制代码
    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中点击一个按钮的事件

[C#] 纯文本查看 复制代码
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界面为什么会卡呢?

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

zbr878458173 发表于 2023-5-18 08:17
让我们来看一下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)(() =&gt; { AppendTextToRichTextBox(RtxConsole,$"[{DateTime.Now.ToString("HH:mm:ss")}]完成文件关闭");
}));
}



在这个修改后的代码中, _StartWriteLog 的内容在一个新的线程中运行,而更新 UI 的代码在 UI 线程中运行,因此 UI 不会在写入操作进行时被阻塞。

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
zhufengwan + 1 + 1 6666666
xiaovssha + 1 + 1 热心回复!

查看全部评分

赵爽 发表于 2023-5-18 08:35
zbr878458173 发表于 2023-5-18 08:17
让我们来看一下AI的回答
这可能是由于在 WinForms 中,所有 UI 更新都需要在 UI 线程上进行。在你的 _Star ...

这真是ai干的
Likey 发表于 2023-5-18 09:05
Broadm 发表于 2023-5-18 09:12
我认为是while循环放在UI线程导致的, 把while循环放在一个单独的线程应该可以解决问题,   while等待了三秒, 也就是阻塞了主线程3秒, 虽然内部用了await, 但是await的执行时间很短, 所以和死循环等同, 导致主线程卡顿3秒
while (DateTime.Now < endTime)
    {
        await _LogAsyncWriter.WriteLogAsync(_OnceWriteText);
        writeCount++;
    }
pjy612 发表于 2023-5-18 09:19
我记得如果 你要 在 其他 线程 更新 UI元素的话 要注意两点。
1.是耗时的任务 用异步或线程处理。
2.更新页面UI 要用委托或回调的形式 让 UI主线程 执行。
不然 就有可能出现 界面假死 直到 任务完成。
oxf5deb3 发表于 2023-5-18 09:23
非UI线程更新UI,请使用begininvoke或者invoke
a952135763 发表于 2023-5-18 09:25
本帖最后由 a952135763 于 2023-5-17 22:36 编辑

[C#] 纯文本查看 复制代码
private void BtnTestLogAsyncWriter_Click(object sender, EventArgs e)

这句代码改成
[C#] 纯文本查看 复制代码
private async void BtnTestLogAsyncWriter_Click(object sender, EventArgs e)

就行了
调用_StartWriteLog的时候 使用await

对了
[C#] 纯文本查看 复制代码
private async void _StartWriteLog()

的void改成 task


原因就是 async void 和 async task的行为问题
async void在事件程序中并不会切换上下文,会让函数在调用方的上下文运行.
所以你调用方是主线程,当主线程的事件循环完成了一次 接下来就要运行你的_StartWriteLog方法  
_StartWriteLog是堵塞的 界面当然卡死了

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
xjwebs + 1 + 1 热心回复!

查看全部评分

larryliao 发表于 2023-5-18 09:26
while 循环中加上 : Application.DoEvents(); 也可以防止界面假死

while (DateTime.Now < endTime)
    {
        await _LogAsyncWriter.WriteLogAsync(_OnceWriteText);
        writeCount++;
         Application.DoEvents();   //让界面响应事件
    }
 楼主| ilovecomputer66 发表于 2023-5-18 11:01
a952135763 发表于 2023-5-18 09:25
[mw_shl_code=csharp,true]private void BtnTestLogAsyncWriter_Click(object sender, EventArgs e)[/mw_sh ...

但按你说这个该,照样卡住的呀
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 22:26

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表