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.