【问题标题】:Avoid blocking the form without using Application.DoEvents() [duplicate]避免在不使用 Application.DoEvents() 的情况下阻止表单 [重复]
【发布时间】:2020-02-01 05:05:35
【问题描述】:

我的程序在计时器事件中显示时间,button 启动一个函数,该函数不断读取文件的内容,直到达到 50 行。

测试文件由不同的process 创建,偶尔会在其上附加一些行。

如何修改程序以避免在执行期间阻塞表单 执行?

WinForm Application UI Hangs during Long-Running Operation 相比的不同之处在于调用的函数必须更新表单的某些元素。

而且我不想使用 Application.DoEvents(), 我的程序中有数百行 Application.DoEvents(),有时它们会造成混乱。

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    void Timer1Tick(object sender, EventArgs e)
    {
        UpdateTime();           
    }

    void UpdateTime()
    {
        DateTime dt = DateTime.Now;
        textBox1.Text = dt.ToString("hh:mm:ss");
    }


    void BtnRunClick(object sender, EventArgs e)
    {
        int nlines = 0;
        while(nlines < 50) {
            nlines = listBox1.Items.Count;
            this.Invoke(new Action(() => ReadLinesFromFile()));         
            Thread.Sleep(1000);
        }
    }   

    void ReadLinesFromFile()
    {
        string sFile = @"D:\Temp1\testfile.txt";
        string[] lines = File.ReadAllLines(sFile);
        listBox1.Items.Clear();
        foreach(string line in lines) {
            listBox1.Items.Add(line);
            listBox1.SelectedIndex = listBox1.Items.Count - 1;
        }
    }
}

【问题讨论】:

  • 不完全。如果我将代码更改为 Task.Run(() => MyRunFunction()); MyRunFunction() 函数不会更新 listBox1。
  • 你启动线程了吗(t.Start())?而且,如果您错过了:“请记住,使用任何线程,您无法更新 GUI,或从后台线程更改任何 GUI 控件。如果您想在 GUI 上执行任何操作,您必须使用 Invoke(和 InvokeRequired)在 GUI 线程上触发该方法。"
  • 当我使用 t.Start() 时,我得到 System.InvalidOperationException: Cross-thread operation not valid: Control 'listBox1' 从创建它的线程以外的线程访问。
  • “如果你想在 GUI 上做任何事情,你必须使用 Invoke(和 InvokeRequired)在 GUI 线程上触发该方法。”
  • 我对代码进行了一些更改,现在后台线程更新了表单上的数据。主要的代码行是 this.Invoke(new Action(() => RunFunction()));但这仍然会阻止表单

标签: c# winforms


【解决方案1】:

IO 操作的异步方法将在同一个 UI 线程上执行所有操作而不阻塞它。

private async void BtnRunClick(object sender, EventArgs e)
{
    int nlines = 0;
    while(nlines < 50) {
        nlines = listBox1.Items.Count;
        await ReadLinesFromFile();
        await Task.Delay(1000);
    }
}   

private async Task ReadLinesFromFile()
{
    var file = @"D:\Temp1\testfile.txt";
    string[] lines = await ReadFrom(file);

    listBox1.Items.Clear();
    foreach(string line in lines) {
        listBox1.Items.Add(line);
        listBox1.SelectedIndex = listBox1.Items.Count - 1;
    }
}

private async Task<string[]> ReadFrom(string file)
{
    using (var reader = File.OpenText(file))
    {
        var content = await reader.ReadToEndAsync();
        return content.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
    }
}

【讨论】:

  • 谢谢法比奥,它运作良好。我对异步运行 void 函数感到困惑,而您的代码解决了这个问题。而且很简单,我会用它作为参考:)
【解决方案2】:

您只需要调用 ui 更新:

void BtnRunClick(object sender, EventArgs e)
{
    new Thread(Run).Start();
}

void Run()
{
    int nlines = 0;
    while (nlines < 50)
    {
        nlines = listBox1.Items.Count;
        ReadLinesFromFile();
        Thread.Sleep(1000);
    }
}

void ReadLinesFromFile()
{
    string sFile = @"D:\Temp1\testfile.txt";
    string[] lines = File.ReadAllLines(sFile);

    listBox1.InvokeOnUIThread(() => {
        listBox1.Items.Clear();
        foreach (string line in lines)
        {
            listBox1.Items.Add(line);
            listBox1.SelectedIndex = listBox1.Items.Count - 1;
        }
    });
}

将此类添加到您的项目中:

public static class ControlExtensions
{
    public static void InvokeOnUIThread(this Control control, Action action)
    {
        if (control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }
}

【讨论】:

  • 谢谢Xiaoy312,你的代码也很好用。这是一个很好的灵感来源:)
【解决方案3】:

您只需要AsyncAwait 标记即可实现此目的。

这可以应用于需要长时间运行不断更新您的用户界面的问题。

下面的 sn-p 在循环中不断更新TextBox.Text,使它看起来像一个计时器。 LongRunningProcess() 模拟采取行动的时间

    async Task LongRunningProcess()
    {

        await Task.Delay(1000);
        
    }

    private async void Button_Click_1(object sender, RoutedEventArgs e)
    {
        for (int i = 0; i < 10; i++)
        {
            DateTime dt = DateTime.Now;
            textBox1.Text = dt.ToString("hh:mm:ss");
            await Task.Run(() => LongRunningProcess());
            
        }
    }

如果您想了解更多关于 C# 中的异步编程的信息,您可以 请参阅以下文章中的 权威 的 Stephen Cleary 字段

https://blog.stephencleary.com/2012/02/async-and-await.html

【讨论】:

  • 很好的例子,感谢分享代码和链接!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-08
  • 1970-01-01
  • 2016-04-12
  • 1970-01-01
  • 1970-01-01
  • 2017-04-12
  • 1970-01-01
相关资源
最近更新 更多