I have this code with TaskCompletionSource. I need to use in my console app. But the point is that it should be finished in background. If I now use await queries.MarkRandomTaskWithDelay(1000) it waits 1000 ms and then I can continue working with app. But I need to continue working immediately and only then to receive result. How can I do it?
public static class queries
{
public static TaskCompletionSource<int> completionSource = new TaskCompletionSource<int>();
public static Task<int> MarkRandomTaskWithDelay(int interval)
{
Timer timer = new Timer(interval)
{
AutoReset = false
};
timer.Elapsed += Marking;
timer.Start();
return completionSource.Task;
}
private async static void Marking(object o, ElapsedEventArgs e)
{
try
{
await SomeWork();
Console.WriteLine($"\nTask status with ID {task.Id} was changed to 'finished' in background.\n");
completionSource.SetResult(task.Id);
}
catch (Exception ex)
{
completionSource.SetException(ex);
}
}
}
Don't use await until you actually need the result.
//Start the task but don't wait for it to finish
Task<int> task = queries.MarkRandomTaskWithDelay(1000);
//Do whatever work we can do without knowing the result
DoOtherWork();
//Now we need the result, so we await the original task
int result = await task;
UseTheResult(result);
Related
I have a WPF app running on .net 6 and an external device connected to it.
Initializing the device sometimes fails and I don't want to hold the UI thread trying to initialize it.
I want to run the following method (_device.Init()) in an async fashion and when it returns true, run Start() method.
edit: run it until it returns true from the _device.Init() method, not true for finishing the task
Is there a built-in functionality to do it with tasks? or any other "best practice" way?
Thank you :)
SomeDevice _device = new();
public async void Init()
{
// some other code
while (Task.Run(() => _device.Init()).Result == false)
{
}
Start();
}
public void Start()
{
// some other code
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
_device.Start();
}));
}
Instead of getting the Result of the Task (which may block the UI thread) you should await the Task:
public async void Init()
{
// some other code
while (!await Task.Run(() => _device.Init()))
{
}
Start();
}
The method should also be awaitable and be awaited when called, e.g. in an async Loaded event handler:
public async Task Init()
{
// some other code
while (!await Task.Run(() => _device.Init()))
{
}
Start();
}
...
await Init();
public async void Init()
{
var task = _device.Init();
//do work here
await task;
Start();
}
Should do the trick, it'll do the work and then wait for the task to complete before going to Start();
If you want to simply wait for init to finish and then run start it's even simpler with
await _device.Init().ContinueWith((x) => { Start();})
I'm trying to asynchronously show a progress form that says the application is running while the actual application is running.
As following this question, I have the following:
Main Form:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
private async void Run_Click(object sender, EventArgs e)
{
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
}
}
Progress Form
public partial class RunningForm : Form
{
private readonly SynchronizationContext synchronizationContext;
public RunningForm()
{
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
}
public async void ShowRunning()
{
this.RunningLabel.Text = "Running";
int dots = 0;
await Task.Run(() =>
{
while (true)
{
UpadateUi($"Running{new string('.', dots)}");
Thread.Sleep(300);
dots = (dots == 3) ? 0 : dots + 1;
}
});
}
public void UpadateUi(string text)
{
synchronizationContext.Post(
new SendOrPostCallback(o =>
{
this.RunningLabel.Text = text;
}),
text);
}
public void CloseThread()
{
synchronizationContext.Post(
new SendOrPostCallback(o =>
{
this.Close();
}),
null);
}
}
internal static class DialogExt
{
public static async Task<DialogResult> ShowDialogAsync(this Form form)
{
await Task.Yield();
if (form.IsDisposed)
{
return DialogResult.OK;
}
return form.ShowDialog();
}
}
The above works fine, but it doesn't work when I'm calling from outside of another from. This is my console app:
class Program
{
static void Main(string[] args)
{
new Test().Run();
Console.ReadLine();
}
}
class Test
{
private RunningForm runningForm;
public async void Run()
{
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.CloseThread();
await progressFormTask;
MessageBox.Show(data.ToString());
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
}
Watching what happens with the debugger, the process gets to await Task.Yield() and never progresses to return form.ShowDialog() and thus you never see the RunningForm. The process then goes to LoadDataAsync() and hangs forever on await Task.Delay(2000).
Why is this happening? Does it have something to do with how Tasks are prioritized (ie: Task.Yield())?
Watching what happens with the debugger, the process gets to await
Task.Yield() and never progresses to return form.ShowDialog() and thus
you never see the RunningForm. The process then goes to
LoadDataAsync() and hangs forever on await Task.Delay(2000).
Why is this happening?
What happens here is that when you do var runningForm = new RunningForm() on a console thread without any synchronization context (System.Threading.SynchronizationContext.Current is null), it implicitly creates an instance of WindowsFormsSynchronizationContext and installs it on the current thread, more on this here.
Then, when you hit await Task.Yield(), the ShowDialogAsync method returns to the caller and the await continuation is posted to that new synchronization context. However, the continuation never gets a chance to be invoked, because the current thread doesn't run a message loop and the posted messages don't get pumped. There isn't a deadlock, but the code after await Task.Yield() is never executed, so the dialog doesn't even get shown. The same is true about await Task.Delay(2000).
I'm more interested in learning why it works for WinForms and not for
Console Applications.
You need a UI thread with a message loop in your console app. Try refactoring your console app like this:
public void Run()
{
var runningForm = new RunningForm();
runningForm.Loaded += async delegate
{
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
};
System.Windows.Forms.Application.Run(runningForm);
}
Here, the job of Application.Run is to start a modal message loop (and install WindowsFormsSynchronizationContext on the current thread) then show the form. The runningForm.Loaded async event handler is invoked on that synchronization context, so the logic inside it should work just as expected.
That however makes Test.Run a synchronous method, i. e., it only returns when the form is closed and the message loop has ended. If this is not what you want, you'd have to create a separate thread to run your message loop, something like I do with MessageLoopApartment here.
That said, in a typical WinForms or WPF application you should almost never need a secondary UI thread.
currently I'm using a timer to poll every x seconds. I've seen that I could also use asyncronous tasks to execute a function after x seconds.
So I've created an example for reproduction. This is how I would use a polling timer
class UseTimer
{
public UseTimer()
{
Console.WriteLine("Foo");
Timer myTimer = new Timer(2000);
myTimer.Elapsed += (object sender, ElapsedEventArgs e) =>
{
Console.WriteLine("Bar");
myTimer.Enabled = false;
};
myTimer.Enabled = true;
Console.ReadLine();
}
}
The code first logs Foo, then waits 2 seconds for the first timer tick and then logs Bar. I tried to reproduce it by using async/await
class UseAsync
{
public UseAsync()
{
Console.WriteLine("Foo");
Do().Wait();
Console.ReadLine();
}
private async Task Do()
{
await Task.Delay(2000);
Console.WriteLine("Bar");
}
}
The behaviour seems to be the same when I test it with this code
class Program
{
static void Main(string[] args)
{
// UseAsync a = new UseAsync();
UseTimer t = new UseTimer();
}
}
I would like to know if I could or even should switch to async because it's easier to maintain and takes out complexity but remains the same way under the hood.
"Every x seconds" is different from "after x seconds".
If you need to run something (repeatedly) every x seconds, use a Timer.
If you need to run something (only once) after x seconds, use Task.Delay.
As noted in the comments, Task.Delay uses a System.Threading.Timer anyway, it's just easier to use for a single wait, and keeps your code clean.
Also, it's not wise to use asynchronous methods in a class constructor. Class constructors cannot be async, and thus you end up blocking the thread (as you did when using Wait()), or "fire and forget". If you need to do anything asynchronous while creating a new object, you can use a "factory method": a static method that creates the object for you. Then you make the constructor private to force everyone to use the factory method:
class UseAsync
{
private UseAsync() {}
public static async Task<UseAsync> CreateUseAsync()
{
var myC = new UseAsync();
await myC.Do();
return myC;
}
private async Task Do()
{
await Task.Delay(2000);
Console.WriteLine("Bar");
}
}
Then you can create an instance like this:
var a = await UseAsync.CreateUseAsync();
I've done this when I need to retrieve data from somewhere before an object is actually useful.
The console.readline should be outside useAsync method, if not the task Do will not be executed
class Program
{
static void Main(string[] args)
{
UseAsync.UseAsyn();
Console.ReadLine();
}
}
static class UseAsync
{
public static async Task UseAsyn()
{
Console.WriteLine("Foo");
await Do();
}
private static async Task Do()
{
await Task.Delay(2000);
Console.WriteLine("Bar");
}
}
I'm trying to asynchronously show a progress form that says the application is running while the actual application is running.
As following this question, I have the following:
Main Form:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
private async void Run_Click(object sender, EventArgs e)
{
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
}
}
Progress Form
public partial class RunningForm : Form
{
private readonly SynchronizationContext synchronizationContext;
public RunningForm()
{
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
}
public async void ShowRunning()
{
this.RunningLabel.Text = "Running";
int dots = 0;
await Task.Run(() =>
{
while (true)
{
UpadateUi($"Running{new string('.', dots)}");
Thread.Sleep(300);
dots = (dots == 3) ? 0 : dots + 1;
}
});
}
public void UpadateUi(string text)
{
synchronizationContext.Post(
new SendOrPostCallback(o =>
{
this.RunningLabel.Text = text;
}),
text);
}
public void CloseThread()
{
synchronizationContext.Post(
new SendOrPostCallback(o =>
{
this.Close();
}),
null);
}
}
internal static class DialogExt
{
public static async Task<DialogResult> ShowDialogAsync(this Form form)
{
await Task.Yield();
if (form.IsDisposed)
{
return DialogResult.OK;
}
return form.ShowDialog();
}
}
The above works fine, but it doesn't work when I'm calling from outside of another from. This is my console app:
class Program
{
static void Main(string[] args)
{
new Test().Run();
Console.ReadLine();
}
}
class Test
{
private RunningForm runningForm;
public async void Run()
{
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.CloseThread();
await progressFormTask;
MessageBox.Show(data.ToString());
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
}
Watching what happens with the debugger, the process gets to await Task.Yield() and never progresses to return form.ShowDialog() and thus you never see the RunningForm. The process then goes to LoadDataAsync() and hangs forever on await Task.Delay(2000).
Why is this happening? Does it have something to do with how Tasks are prioritized (ie: Task.Yield())?
Watching what happens with the debugger, the process gets to await
Task.Yield() and never progresses to return form.ShowDialog() and thus
you never see the RunningForm. The process then goes to
LoadDataAsync() and hangs forever on await Task.Delay(2000).
Why is this happening?
What happens here is that when you do var runningForm = new RunningForm() on a console thread without any synchronization context (System.Threading.SynchronizationContext.Current is null), it implicitly creates an instance of WindowsFormsSynchronizationContext and installs it on the current thread, more on this here.
Then, when you hit await Task.Yield(), the ShowDialogAsync method returns to the caller and the await continuation is posted to that new synchronization context. However, the continuation never gets a chance to be invoked, because the current thread doesn't run a message loop and the posted messages don't get pumped. There isn't a deadlock, but the code after await Task.Yield() is never executed, so the dialog doesn't even get shown. The same is true about await Task.Delay(2000).
I'm more interested in learning why it works for WinForms and not for
Console Applications.
You need a UI thread with a message loop in your console app. Try refactoring your console app like this:
public void Run()
{
var runningForm = new RunningForm();
runningForm.Loaded += async delegate
{
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
};
System.Windows.Forms.Application.Run(runningForm);
}
Here, the job of Application.Run is to start a modal message loop (and install WindowsFormsSynchronizationContext on the current thread) then show the form. The runningForm.Loaded async event handler is invoked on that synchronization context, so the logic inside it should work just as expected.
That however makes Test.Run a synchronous method, i. e., it only returns when the form is closed and the message loop has ended. If this is not what you want, you'd have to create a separate thread to run your message loop, something like I do with MessageLoopApartment here.
That said, in a typical WinForms or WPF application you should almost never need a secondary UI thread.
I Have at least 20 threads that each does some operations every x minutes. My issue here is that I can use timers with interval, which I register a delegate on their ElapsedEventHandler and in that method I do my operation with Thread or Task . BTW I can manage timer's start, stop and interval easily
Is this the best way ?
Is there any other solution or other choices in .NET?
for example :
List<IpeTimer> Timers = new List<IpeTimer>();
Timers.Add(new IpeTimer(){ TimerName = "timer1" ,Timer = new System.Timers.Timer()});
Timers[0].Timer.Interval = 4000;
Timers[0].Timer.Elapsed += new ElapsedEventHandler(TimerEventProcessor1);
Timers[0].Timer.Start();
Timers.Add(new IpeTimer() { TimerName = "timer2", Timer = new System.Timers.Timer() });
Timers[1].Timer.Interval = 5000;
Timers[1].Timer.Elapsed += new ElapsedEventHandler(TimerEventProcessor2);
Timers[1].Timer.Start();
and
public static void TimerEventProcessor1(object sender, ElapsedEventArgs e)
{
Task t2 = new Task(() =>
{
Console.WriteLine("1");
}
, TaskCreationOptions.LongRunning);
t2.Start();
}
public static void TimerEventProcessor2(object sender, ElapsedEventArgs e)
{
Task t2 = new Task(() =>
{
Console.WriteLine("2");
}
, TaskCreationOptions.LongRunning);
t2.Start();
}
None of the above.
Use an async method that awaits a Task.Delay (which internally uses a Threading.Timer). That way the interval you wait will not depend on how long the operation takes (i.e. if the timer's interval is 2 seconds but it takes 1 second to run there will only be a second in which no operation runs):
async Task DoSomething(CancellationToken token)
{
while (true)
{
// do something
await Task.Delay(1000, token);
}
}
I came up with this solution by using your code.
public static CancellationToken Token { get; private set; }
public static void Cancel()
{
Token = new CancellationToken(true);
}
async static Task DoSomething(int interval)
{
while (true)
{
Console.WriteLine("1");
await Task.Delay(interval, Token);
}
}
private static void Main(string[] args)
{
DoSomething(1000);
Thread.Sleep(5000);
Cancel();
Console.ReadLine();
}