-1

My program displays the time in a timer event and a button starts a function that keeps reading the content of a file until it reaches 50 lines.

The test file is created by a different process that once in a while appends some lines to it.

How can I modify the program to avoid blocking the form during execution ?

The difference compared to WinForm Application UI Hangs during Long-Running Operation is that the functions called have to update some elements of the form.

And I don't want to use Application.DoEvents(), I have hundreds of lines of Application.DoEvents() in my programs and sometimes they create a mess.

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;
        }
    }
}
6
  • Not quite. If I change the code to Task.Run(() => MyRunFunction()); MyRunFunction() function will not update the listBox1. Commented Feb 1, 2020 at 5:23
  • Did you start the thread (t.Start())? And, in case you've missed: "Remember that with anything threaded, you cannot update the GUI, or change any GUI controls from a background thread. If you want to do anything on the GUI you have to use Invoke (and InvokeRequired) to trigger the method back on the GUI thread." Commented Feb 1, 2020 at 5:29
  • When I use t.Start(), I get System.InvalidOperationException: Cross-thread operation not valid: Control 'listBox1' accessed from a thread other than the thread it was created on. Commented Feb 1, 2020 at 5:30
  • "If you want to do anything on the GUI you have to use Invoke (and InvokeRequired) to trigger the method back on the GUI thread." Commented Feb 1, 2020 at 5:34
  • I mde some changes to the code and now the background thread updates the data on the form. The main line of code is this.Invoke(new Action(() => RunFunction())); but that still blocks the form Commented Feb 1, 2020 at 5:48

3 Answers 3

2

Asynchronous approach for IO operation will execute all operations on the same UI thread without blocking it.

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);
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you Fabio, it works well. I was confused about asynchronously running void functions, and your code addresses that. And it is simple enough, I will use it as a reference :)
2

You need only to invoke the ui updates:

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

Add this class to your project:

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

1 Comment

Thank you Xiaoy312, your code also works well. It's a good source for inspiration :)
1

All you just need is Async and Await markers to achieve this.

This you can apply to problems which have a long running operation that continuously updates your UI.

The below snippet continuously updates TextBox.Text in a loop, giving it the appearance of a timer. LongRunningProcess() simulates a time taking action

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

If you want to know more about Asynchrnous programming in C# you can refer to below article by Stephen Cleary who is THE Authority in this field

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

1 Comment

Excellent example, thanks for sharing the code and the link!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.