0

I have read some stuff about async/await, but I can't, for the life of me, understand how it works.

My scenario is quite simple : I click on a button which display a file picker. Once a file is picked, I read it line by line to do some data management. The file loads quickly but the processing takes some time, so I'd like to have a progress bar or something - at least my UI should not be frozen.

I tried putting async or await there and there : nothing's working and I'd like to understand what I'm supposed to do, in a proper way.

private async void BtnLoadFile_Clicked(object sender, EventArgs e)
{
    var result = await FilePicker.Default.PickAsync();
    if (result != null)
    {
        // Read the stream - this is quick
        Stream s = await result.OpenReadAsync();

        // Treat the file - this need to run on another thread 
        DoLenghtyStuff(s);
    }
}

private void DoLenghtyStuff(Stream s)
{
    // Do some long stuff here, update the UI with a progress bar
}

I started some digging and stumbled on this post :How and when to use ‘async’ and ‘await’

So I did some changes :

private async void BtnLoadFile_Clicked(object sender, EventArgs e)
{
    var result = await FilePicker.Default.PickAsync();
    if (result != null)
    {
        // Read the stream - this is quick
        Stream s = await result.OpenReadAsync();

        // Treat the file - this need to run on another thread 
        await DoLenghtyStuff(s);
    }
}

private async Task<int> DoLenghtyStuff(Stream s)
{
    // Do some long stuff here
    await Task.Delay(1); // 1 second delay

    return 1;
}

... to no avail :it's still freezing and doing an await Task.Delay(1); does not seem to be the way to go.

Update : I used Task.Run() but have an error : "Only the original thread that created a view hierarchy can touch its views". It seems its when I try to update Label.Text. Funny enough, displaying updates in a ProgressBar element works ok though.

Here's my new code :

private async void BtnLoadFile_Clicked(object sender, EventArgs e)
{
    var result = await FilePicker.Default.PickAsync();
    if (result != null)
    {
        invalidLines = new List<string>();
        chatList = new List<ChatLine>();

        // Do Stuff with the file
        Stream s = await result.OpenReadAsync();

        // Treat the file
        await Task.Run(() => { FileHandler(s); });
    }
    else
    {
        // Display the logs
        frmLogs.IsVisible = true;
        frmLogs.BackgroundColor = Color.FromArgb("b47d7d");
        lblLogs.Text = $"aborted by the user";
    }
}
private void FileHandler(Stream s)
{
    int realLines = 0;
    int keptLines = 0;
    int totalLines = 0;


    //using (StreamReader sr = new StreamReader(s, Encoding.GetEncoding("iso-8859-1")))
    using StreamReader sr = new(s, Encoding.GetEncoding("UTF-8"));

    // Read 
    while ((sr.ReadLine()) != null)
        totalLines++;

    // Reset position
    s.Position = 0;
    sr.DiscardBufferedData();        // reader now reading from position 0

    string currentLine;

    // Read first line to skip it
    currentLine = sr.ReadLine();
    realLines++;

    while ((currentLine = sr.ReadLine()) != null)
    {
        realLines++;

        // Progress bar
        prgBarLines.Progress = ((double)realLines / totalLines);
        lblProgress.Text = $"{Math.Round((double)realLines / totalLines * 100)}%"; // <= This crashes

        // Long Data processing here : Regex checks, etc...
    }
}
Beb
  • 67
  • 6
  • 5
    What is `DoLenghtyStuff()` doing? No asynchronous methods there? If that's the case, you can `Task.Run()` it in your Button click handler and await that call (you should disable the Button until those methods return) – Jimi May 12 '23 at 16:38
  • 1
    where is the code for "progress bar or something"? What exactly does "nothings working" mean? Is the code literally not running, or hanging, or what? – Jason May 12 '23 at 16:42
  • It hangs. I used Task.Run() using a progress bar and a label to display the percentage of the progress, but it crashes when setting the label's text. I updated my code in the original post. – Beb May 12 '23 at 17:23
  • 1
    Get acquainted with the [IProgress](https://learn.microsoft.com/en-us/dotnet/api/system.iprogress-1) delagates – Jimi May 12 '23 at 17:29
  • 1
    you can only update the UI from the UI thread. Use `MainThread` to do this – Jason May 12 '23 at 18:33
  • 1
    Additional link about how to use `IProgress` to report a progress https://stackoverflow.com/a/15408205/2011071 – Serg May 12 '23 at 18:47

2 Answers2

-1

Thank you all for your answers.

So in a nutshell :

  1. I just needed to call put my method using await Task.Run(() => { FileHandler(s); });
  2. In FileHandler, execute line code that uses the UI elements with MainThread.BeginInvokeOnMainThread

Here's my final code :

private async void BtnLoadFile_Clicked(object sender, EventArgs e)
{

    var result = await FilePicker.Default.PickAsync();
    if (result != null)
    {
        // Do Stuff with the file
        Stream s = await result.OpenReadAsync();

        // Treat the file v
        await Task.Run(() => { FileHandler(s); });
    }
}

And the method itself. No need to make it async. Important, as mentionned in the comments : to refresh parts of the UI (like Labels, Entries, etc...), use MainThread.BeginInvokeOnMainThread(() => { /* Your Code Here */ });

// Handle the file
private void FileHandler(Stream s)
{
    // Open the file
    using StreamReader sr = new(s, Encoding.GetEncoding("UTF-8"));

    // Read the total lines
    int totalLines = 0;
    while ((sr.ReadLine()) != null)
        totalLines++;

    // Reset position
    s.Position = 0;
    sr.DiscardBufferedData();        // reader now reading from position 0

    // Read first line to skip it
    string currentLine;
    currentLine = sr.ReadLine();
    realLines++;

    while ((currentLine = sr.ReadLine()) != null)
    {
        realLines++;

        // Progress bar (does not need a BeginInvokeOnMainThread somehow)
        prgBarLines.ProgressTo((double)realLines / totalLines, 1, Easing.Linear);

        // Update progress on the UI :
        MainThread.BeginInvokeOnMainThread(() => { lblProgress.Text = $"{Math.Round((double)realLines / totalLines * 100)}%"; });

        // Do Lenghty stuff..
        // ...
    }

    // Long treatment is done. Reporting is shown in the UI, so call the methode using BeginInvokeOnMainThread
    MainThread.BeginInvokeOnMainThread(() => { PerformPostLoadActions(); });
}
Beb
  • 67
  • 6
-2

I think your problem is you are telling the compiler to wait for DoLenghtyStuff to finish. Try removing the await from the call to DoLenghtyStuff(s);

As for the task.run I use this pattern all the time.

var t = Task.Run(() => { /* Your code here */ });
Task UITask = t.ContinueWith(_ =>
    {
        /* your code here that runs in UI thread after Task.Run is finished */
    }, TaskScheduler.FromCurrentSynchronizationContext());
Terry
  • 11
  • 3
  • Well, I need during the Task to update the UI on the run to show progress in my UI, I guess I won't be able to use this syntax I'm afraid – Beb May 12 '23 at 17:56
  • Answer description is poor. `await` isn’t the problem, it is running lengthy code while still on main thread. In an async environment, `await Task.Run` works identically to this more complex code. `await` does the continuation. – ToolmakerSteve May 12 '23 at 18:48
  • 1
    @Bob inside Task.Run, use MainThread.BeginInvoke… call wrapped around code that affects UI. – ToolmakerSteve May 12 '23 at 18:49
  • Wow, sorry for my poor answer. I'll crawl back in my hole and keep to myself. – Terry May 12 '23 at 20:14