-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;
        }
    }
}
Clint
  • 6,011
  • 1
  • 21
  • 28
Nick_F
  • 1,103
  • 2
  • 13
  • 30
  • Not quite. If I change the code to Task.Run(() => MyRunFunction()); MyRunFunction() function will not update the listBox1. – Nick_F Feb 01 '20 at 05: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." – Xiaoy312 Feb 01 '20 at 05: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. – Nick_F Feb 01 '20 at 05: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." – Xiaoy312 Feb 01 '20 at 05: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 – Nick_F Feb 01 '20 at 05:48
  • Async vs UI thread has evolved quite a lot over time. However, the first duplicate marked above has a wealth of information covering all of this evolution. The general techniques behind handling long-running tasks asynchronously while still giving the UI thread a chance to update are well-documented. – Peter Duniho Feb 01 '20 at 07:31

3 Answers3

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);
    }
}
Fabio
  • 31,528
  • 4
  • 33
  • 72
  • 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 :) – Nick_F Feb 01 '20 at 06:13
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();
        }
    }
}
Xiaoy312
  • 14,292
  • 1
  • 32
  • 44
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

Community
  • 1
  • 1
Clint
  • 6,011
  • 1
  • 21
  • 28