Home > Blockchain >  An async method doesn't perform as per spec while it is called from form's constructor (.N
An async method doesn't perform as per spec while it is called from form's constructor (.N

Time:10-13

I tested two apps (.NET6.0 Win10).

App1:

namespace TestConsoleApp;
class Program
{
    static bool Flag;
    static async void Wd()
    {
        Console.WriteLine("Wd starts "   DateTime.Now.ToString("ss.ffff"));
        Console.WriteLine("Wd Thread ID "   Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(5000);
        Flag = false;
        Console.WriteLine("Wd ends "   DateTime.Now.ToString("ss.ffff"));
        Console.WriteLine("Wd Thread ID "   Thread.CurrentThread.ManagedThreadId) ;
    }
    static void Main()
    {
        int i = 60;
        Flag = true;
        Console.WriteLine("Start "   DateTime.Now.ToString("ss.ffff"));
        Console.WriteLine("Main Thread ID "   Thread.CurrentThread.ManagedThreadId);
        Wd();
        while (i-- > 0 && Flag) Task.Delay(100).Wait();
        Console.WriteLine("End "   i.ToString()   " "   DateTime.Now.ToString("ss.ffff"));
    }
}

Output 1:

Start 21.2212

Main Thread ID 1

Wd starts 22.0262

Wd Thread ID 1

Wd ends 27.0454

Wd Thread ID 5

End 19 27.0904

It performed as expected (the watchdog breaks the while loop in 5 s). A new thread is created implicitly at the expected point.

App2:

namespace WinFormsAppTaskDelay;
internal static class Program
{
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.Run(new Form1());
    }
}
class Form1:Form
{
    bool Flag;
    async void Wd()
    {
        label4.Text = $"Wd start{DateTime.Now:ss.ffff}";
        label7.Text = $"Thread ID {Thread.CurrentThread.ManagedThreadId}";
        await Task.Delay(5000); Flag = false;
        label9.Text = $"Thread ID {Thread.CurrentThread.ManagedThreadId}";
        label2.Text = $"Wd exit {DateTime.Now:ss.ffff}";
    }

    public Form1()
    {
        InitializeComponent(); int i = 60; Flag = true;
        label1.Text = $"Start {DateTime.Now:ss.ffff}";
        label6.Text = $"Thread ID {Thread.CurrentThread.ManagedThreadId}";
        Wd();
        //Task.Run(Wd);
        label5.Text = $"Check point {DateTime.Now:ss.ffff}";
        label8.Text = $"Thread ID {Thread.CurrentThread.ManagedThreadId}";
        while (i-- > 0 && Flag) Task.Delay(100).Wait();
        label3.Text = $"End {i} {DateTime.Now:ss.ffff}";
    }

    private System.ComponentModel.IContainer components = null;
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null)) { components.Dispose(); }
        base.Dispose(disposing);
    }
    private void InitializeComponent()
    {
        label1 = new Label(); label2 = new Label(); label3 = new Label(); label4 = new Label(); label5 = new Label();
        label6 = new Label(); label7 = new Label(); label8 = new Label(); label9 = new Label();
        SuspendLayout();
        label1.Location = new Point(33, 25); label1.Text = label1.Name = "label1";
        label1.Size = new Size(38, 15); label1.TabIndex = 0;
        label2.Location = new Point(33, 141); label2.Text = label2.Name = "label2";
        label2.Size = new Size(38, 15); label2.TabIndex = 1;
        label3.Location = new Point(33, 187); label3.Text = label3.Name = "label3";
        label3.Size = new Size(38, 15); label3.TabIndex = 2;
        label4.Location = new Point(33, 60); label4.Text = label4.Name = "label4";
        label4.Size = new Size(38, 15); label4.TabIndex = 3;
        label5.Location = new Point(33, 100); label5.Text = label5.Name = "label5";
        label5.Size = new Size(38, 15); label5.TabIndex = 4;
        label6.Location = new Point(227, 22); label6.Text = label6.Name = "label6";
        label6.Size = new Size(38, 15); label6.TabIndex = 5;
        label7.Location = new Point(227, 60); label7.Text = label7.Name = "label7";
        label7.Size = new Size(38, 15); label7.TabIndex = 6;
        label8.Location = new Point(227, 100); label8.Text = label8.Name = "label8";
        label8.Size = new Size(38, 15); label8.TabIndex = 7;
        label9.Location = new Point(227, 141); label9.Text = label9.Name = "label9";
        label9.Size = new Size(38, 15); label9.TabIndex = 8;
        label1.AutoSize = label2.AutoSize = label3.AutoSize = label4.AutoSize = label5.AutoSize =
            label6.AutoSize = label7.AutoSize = label8.AutoSize = label9.AutoSize = true;
        AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(460, 233);
        Controls.Add(label9); Controls.Add(label8); Controls.Add(label7); Controls.Add(label6); Controls.Add(label5);
        Controls.Add(label4); Controls.Add(label3); Controls.Add(label2); Controls.Add(label1);
        Text = Name = "Form1"; ResumeLayout(false); PerformLayout();
    }
    private Label label1; private Label label2; private Label label3; private Label label4; private Label label5;
    private Label label6; private Label label7; private Label label8; private Label label9;
}

Output 2: App2 output 1

Here the watchdog method doesn't work as expected. I expect the behavior same as in app1. We can see the new thread is not created for Wd.

With the changed constructor

        //Wd();

        Task.Run(Wd);

it works differently (as expected), the new thread is created and the watchdog is fired, but the async method works as a new thread, not as the true async one.

Output 3: App2 output 2

The output 2 behavior is still unclear for me. Async/await is expected to work, but no.

CodePudding user response:

The difference is the synchronization context (or lack thereof). After await, the synchronization context will define how the rest of the method runs.

The synchronization context of WinForms is the UI thread.

A console application does not have a synchronization context, so the rest of the method can be run on any ThreadPool thread.

You can find a list of the default SynchronizationContext for different application types in the MSDN article Parallel Computing - It's All About the SynchronizationContext.

You can use .ConfigureAwait(false) to specifically tell it that you don't need to resume on the synchronization context:

await Task.Delay(5000).ConfigureAwait(false);

And in that case, the WinForms application will behave the same as the console app.

Some people like to use ConfigureAwait(false) just about everywhere they can. I suggest you don't get into that habit (except if you're writing a third-party library), for reasons I wrote about here: .NET: Don’t use ConfigureAwait(false)

The Microsoft documentation for async/await is a great introduction. Read through that series of articles, which starts here: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

CodePudding user response:

The way to perform a task asynchronously in the form:

    ...
    label4.Text = $"Wd start{DateTime.Now:ss.ffff}";
    label7.Text = $"Thread ID {Thread.CurrentThread.ManagedThreadId}";
    Task.Run(Wd);
    ...

    void Wd()
    {
        Task.Delay(5000).Wait(); Flag = false;
        Invoke(ProvideResults, Thread.CurrentThread.ManagedThreadId, DateTime.Now);
    }
    void ProvideResults(int thrid, DateTime dt) { label9.Text = $"Thread ID {thrid}"; label2.Text = $"Wd exit {dt:ss.ffff}"; }

'label.Text = ...' is a sample code here.

In this solution the context is explicitly defined by creating a task and then come back to form's context by Invoke.

  • Related