Show elapsed time of a function inside Task.Run C# - c#

I want to show in textbox the time execution of a function that is called using Task.Run, since takes some time
to complete and I'd created a thread for that.
The issue is when I click on begin button is printed inmediately the time in textBox1 and I want to show the
Elapsed time but only just after MyFunction completes the processing or when Cancel button is pressed.
Where should go sw.Stop()?
My current code for Begin and cancel button is:
void Begin_Click(object sender, EventArgs e)
{
Stopwatch sw = Stopwatch.StartNew();
// Pass the token to the cancelable operation.
cts = new CancellationTokenSource();
Task.Run(() => MyFunction(inputstring, cts.Token), cts.Token);
sw.Stop();
textBox1.Text += Math.Round(sw.Elapsed.TotalMilliseconds / 1000, 4) + " sec";
}
void Cancel_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts.Cancel();
cts = null;
}
}

You're not waiting for the MyFunction to complete, You're simply calculating the start time of the Task.Run call. To wait for the MyFunction to complete, you can await the Task returned by Task.Run.
async void Begin_Click(object sender, EventArgs e)//<--Note the async keyword here
{
Stopwatch sw = Stopwatch.StartNew();
// Pass the token to the cancelable operation.
cts = new CancellationTokenSource();
await Task.Run(() => MyFunction(inputstring, cts.Token), cts.Token);//<--Note the await keyword here
sw.Stop();
textBox1.Text += Math.Round(sw.Elapsed.TotalMilliseconds / 1000, 4) + " sec";
}
If you're new to asynchronous programming start by reading here and here

Related

Delay between SemaphoreSlim.Release() and SemaphoreSlim.WaitAsync()

I'm testing a method involving a SemaphoreSlim to notify when an event has occured. It works as intended, but I've noticed that if I use this within the first few seconds of my application starting there is a large delay between the release of the semaphore and the continuation of my code waiting for the semaphore.
main:
var userIntentProvider = new UserIntentProvider();
Console.WriteLine("started");
Task.Run(() =>
{
Console.WriteLine("starting");
//wait for an event to be added.
userIntentProvider.GetNextUserIntentAsync().Wait();
});
while (true)
{
var input = Console.ReadLine();
//add the event
userIntentProvider.AddUserIntent("bar");
}
class:
public void AddUserIntent(string userIntent)
{
NewUserIntent?.Invoke(this, userIntent);
}
public async Task GetNextUserIntentAsync(CancellationToken token = new CancellationToken())
{
var sw = new Stopwatch();
//create signal
var signal = new SemaphoreSlim(0, 1);
//create event handler
EventHandler<string> eventHandler = null;
eventHandler = (sender, e) =>
{
NewUserIntent -= eventHandler;
signal.Release(); //THE SEMAPHORE IS RELEASED AND THE STOPWATCH STARTED
sw.Start();
};
//wrap in try catch to force removal of event
try
{
//subscribe
NewUserIntent += eventHandler;
// wait for signal
await signal.WaitAsync();//THE SEMAPHORE IS WAITED ON AND THE STOPWATCH STOPPED
sw.Stop();
Console.WriteLine("done" + sw.ElapsedMilliseconds);
}
finally
{
NewUserIntent -= eventHandler;
}
}
The Stopwatch measuring the time between the Release() and await WaitAsync() calls can be up to 1.5s. If I wait for a few seconds after starting the application and then trigger this mechanism the stopwatch reports 0ms have passed (more like I was expecting) what could be causing this delay?
Edit: This is running in a console app.

Asynchronous function call of Windows Forms C#

I have an asynchronous function. It is called only once when the form is displayed for the first time. My function should ping devices asynchronously when I open the program. But it turns out that when you close the child form, another poll is launched. Tell me where the error may be.
Function call (I tried to call it in formLoad):
private async void MainForm_Shown(object sender, EventArgs e)
{
await Start();
}
Function itself:
public async Task Start()
{
while (keyOprosDev)
{
for (int i = 0; i < devicesListActivity.Count; i++)
{
devicesListActivity[i].DevicesList.DevicesTotalPing++;
string ipAdresDevice = devicesListActivity[i].DevicesList.DevicesName;
int portDevice = devicesListActivity[i].DevicesList.DevicesPort;
int activeDevice = devicesListActivity[i].DevicesList.DevicesActiv;
int sendTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeSend;
int respTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeResp;
using (TcpClient client = new TcpClient())
{
if (activeDevice == 1)
{
client.SendTimeout = sendTimeDevice;
client.ReceiveTimeout = respTimeDevice;
var ca = client.ConnectAsync(ipAdresDevice, portDevice);
await Task.WhenAny(ca, Task.Delay(sendTimeDevice));
client.Close();
if (ca.IsFaulted || !ca.IsCompleted)
{
textBox1.AppendText($"{DateTime.Now.ToString()} Server refused connection." + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
textBox1.AppendText("\r\n");
devicesListActivity[i].DevicesList.DevicesImage = 1;
}
else
{
devicesListActivity[i].DevicesList.DevicesSuccessPing++;
textBox1.AppendText($"{DateTime.Now.ToString()} Server available" + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
textBox1.AppendText("\r\n");
devicesListActivity[i].DevicesList.DevicesImage = 2;
}
}
else
{
}
}
await Task.Delay(interval);
}
}
}
And here is the opening of the child form:
try
{
DbViewer dbViewer = new DbViewer();
dbViewer.FormClosed += new FormClosedEventHandler(refr_FormClosed);
dbViewer.ShowDialog();
}
catch (Exception ex)
{
writeEventInDb(ex.Message);
}
This is the event that handles the closure of the child form:
void refr_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
kryptonTreeView1.Nodes[0].Nodes[0].Nodes.Clear();
kryptonTreeView1.Nodes[0].Nodes[1].Nodes.Clear();
loadIpListFromDb();
loadComListFromDb();
kryptonTreeView1.ExpandAll();
}
catch (Exception ex)
{
writeEventInDb(ex.Message);
}
}
You need to pass a cancellation token in. Somewhere outside of this code you need to create a CancellationTokenSource the best place is probably an property of the form:
class MainForm
{
CancellationTokenSource cts;
...
You then initialize this and pass this to Start():
private async void MainForm_Shown(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
await Start(ct);
}
In your start loop you need to monitor for the cancellation token:
Because you're using a delay to timeout the ConnectAsync() you need the Task.Delay() to know when cancellation is requested so you need to pass the token to Task.Delay():
await Task.WhenAny(ca, Task.Delay(sendTimeDevice,ct));
After the TcpClient.Close() you need to test if the cancellation is requested, and stop loops if it is:
if (ct.IsCancellationRequested)
break;
You'll need to perform the same test in the while loop, and also you should perform it immediately before the ConnectAsync(). While the most likely place you will encounter ct.IsCancellationRequested == true will be immediately after the Task.WhenyAny or immediately after the Loop interval there's no point starting a ConnectAsync() if cancellation has been requested.
You should also pass the CancellationToken to the Loop interval, otherwise you could end up waiting interval before your form closes:
// This will throw an OperationCancelled Exception if it is cancelled.
await Task.Delay(interval,ct);
Because you're going to continue anyway and just exit if Cancellation is registered you could avoid writing a separate try/catch that does nothing and await the interval like this, it's almost certainly less efficient, but it's cleaner.
// Leave any exceptions of Task.Delay() unobserved and continue
await Task.WhenAny(Task.Delay(interval,ct));
Finally you need to dispose of the CancellationTokenSource, I guess you would do this in something like a MainForm_Closed() function?
private void MainForm_Closed(object sender, EventArgs e)
{
cts.Dispose();
The only thing left to do is to work out when you want to fire the CancellationRequest, based on what you have said you want to do this when the form close button has been clicked, so:
private void MainForm_Closing(object sender, EventArgs e)
{
cts.Cancel();
That will cause the CancellationToken to transition to a cancelled state and your Start() routine will see that and exit.
In your code there is no single place to check for the CancellationToken being set, the rule of thumb is to check for it before and after any await and in your case you should check for it in both the while and the for loop.

Updating status strip label async/await

I have a WinForm, with a toolStripStatusLabel. There is a button, which spawns a new thread to perform its task. The status label needs to update during, and after this task is completed. The GUI elements are in the main thread. If I want to achieve this, can I place the relevant lines to update the label where the comments are below in the code snippet below? Also, I need to have another form open when this label is clicked. From my understanding of asynchronous coding, this should be straightforward, involving an event handler for the label, and the fact that control will return to the caller of the async method. Is this correct? I am relatively new to multithreaded and asynchronous programming, so I am quite confused.
// this is running in the main GUI thread
private async void Export_execute_Click(object sender, System.EventArgs args)
{
try
{
await System.Threading.Tasks.Task.Run(() => do_export(filename, classes, System.TimeZoneInfo.ConvertTimeToUtc(timestamp)));
// if this is successful, status label should be update (task successful)
}
catch (System.Exception e)
{
// status label should be updated (task failed)
}
}
If there is something literally awaitable in the Export method then I think to make it an async method would be better.
private async void Export_execute_Click(object sender, EventArgs e)
{
try
{
await ExportAsync("file1", "classA", DateTime.Now);
toolStripStatusLabel.Text = $"Export finished at {DateTime.Now}";
}
catch (Exception ex)
{
toolStripStatusLabel.Text = $"Export failed, {ex.ToString()}";
}
}
private async Task ExportAsync(string fileName, string classes, DateTime timestamp)
{
toolStripStatusLabel.Text = $"Export start at {timestamp}";
await Task.Delay(TimeSpan.FromSeconds(5));
toolStripStatusLabel.Text = $"Have first half done {timestamp}";
await Task.Delay(TimeSpan.FromSeconds(5));
}
private void toolStripStatusLabel_Click(object sender, EventArgs e)
{
Form2 frm2 = new Form2();
frm2.Show();
}
The standard way to report progress is to use the IProgress<T> interface. There is already an implementation of this interface that you can use (Progress<T>), and is generic so that you can supply any type of argument you want. In the example bellow the argument is a string. The key point is that the event Progress.ProgressChanged is running in the UI thread, so you don't have to worry about it.
// This will run in the UI thread
private async void Export_Execute_Click(object sender, EventArgs args)
{
try
{
var progress = new Progress<string>();
progress.ProgressChanged += ExportProgress_ProgressChanged;
// Task.Factory.StartNew allows to set advanced options
await Task.Factory.StartNew(() => Do_Export(filename, classes,
TimeZoneInfo.ConvertTimeToUtc(timestamp), progress),
CancellationToken.None, TaskCreationOptions.LongRunning,
TaskScheduler.Default);
toolStripStatusLabel.Text = $"Export completed successfully";
}
catch (Exception e)
{
toolStripStatusLabel.Text = $"Export failed: {e.Message}";
}
}
// This will run in the UI thread
private void ExportProgress_ProgressChanged(object sender, string e)
{
toolStripStatusLabel.Text = e;
}
// This will run in a dedicated background thread
private void Do_Export(string filename, string classes, DateTime timestamp,
IProgress<string> progress)
{
for (int i = 0; i < 100; i += 10)
{
progress?.Report($"Export {i}% percent done");
Thread.Sleep(1000);
}
}
How about a BackgroundWorker instead of your current Task? I prefer these because they allow easy communication between the main thread and the worker.
Note that Export_execute_Click is no longer marked as async in this scenario.
Example:
private void Export_execute_Click(object sender, System.EventArgs args) {
// Method level objects are accessible throughout this process
bool error = false;
// Process
BackgroundWorker worker = new BackgroundWorker {
WorkerReportsProgress = true
};
// This executes on main thread when a progress is reported
worker.ProgressChanged += (e, ea) => {
if (ea.UserState != null) {
// ea.UserState.ToString() contains the string progress message
}
};
// This executes as an async method on a background thread
worker.DoWork += (o, ea) => {
try {
var response = do_export(filename, classes, System.TimeZoneInfo.ConvertTimeToUtc(timestamp)));
if (response == whatever) {
worker.ReportProgress(0, "Response from do_export() was `whatever`");
} else {
worker.ReportProgress(0, "Response from do_export() was something bad");
error = true;
}
} catch (System.Exception e) {
worker.ReportProgress(0, $"do_export() failed: {e}");
}
};
// This executes on the main thread once the background worker has finished
worker.RunWorkerCompleted += async (o, ea) => {
// You can communicate with your UI normally again here
if (error) {
// You had an error -- the exception in DoWork() fired
} else {
// You're all set
}
// If you have a busy-indicator, here is the place to disable it
// ...
};
// I like to set a busy-indicator here, some sort of ajax-spinner type overlay in the main UI, indicating that the process is happening
// ...
// This executes the background worker, as outlined above
worker.RunWorkerAsync();
}

Add cancellation token to an async task with progress

I am using below code to do a time consuming operation in a WPF page async way along with progress reporting to the UI
private void btnStart_Click(object sender, RoutedEventArgs e)
{
txt_importStatus.Text = "";
var progress = new Progress<string>(progress_info =>
{
//show import progress on a textfield
txt_importStatus.Text = progress_info + Environment.NewLine + "Please dont close this window while the system processing the excel file contents";
});
// DoProcessing is run on the thread pool.
await Task.Run(() => DoProcessing(progress));
}
public void DoProcessing(IProgress<string> progress)
{
//read an excel file and foreach excel file row
foreach(excelrow row in excelrowlist)
{
//do db entry and update UI the progress like
progress.Report("Processed x number of records. please wait..");
}
}
Now i would like to add an extra option of cancel this async operation in the middle .For doing so i found that i have to add below options
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
private void btnCacnel_Click(object sender, RoutedEventArgs e)
{
tokenSource.Cancel();
}
But how can i pass this tokenSource to my DoProcessing call and how can i handle cancellation inside DoProcessing
You actually don't need to pass the CancellationTokenSource to DoProcessing, but just the CancellationToken.
In order to handle the cancellation you could do something like this:
public void DoProcessing(CancellationToken token, IProgress<string> progress)
{
//read an excel file and foreach excel file row
foreach(excelrow row in excelrowlist)
{
if(token.IsCancellationRequested)
break;
//do db entry and update UI the progress like
progress.Report("Processed x number of records. please wait..");
}
}
In this case you'll need to create the cancellation token source already in btnStart_Click. In case it wasn't clear, you'd need to do like this:
CancellationTokenSource tokenSource;
private void btnStart_Click(object sender, RoutedEventArgs e)
{
txt_importStatus.Text = "";
var progress = new Progress<string>(progress_info =>
{
//show import progress on a textfield
txt_importStatus.Text = progress_info + Environment.NewLine + "Please dont close this window while the system processing the excel file contents";
});
// DoProcessing is run on the thread pool.
tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
await Task.Run(() => DoProcessing(token, progress));
}

Confusion on correct use of Async / Await

This is my implementation of async/await in Windows Forms Application
async Task<int> DoAysnc1()
{
await Task.Delay(3000);
return 3000;
}
async Task<int> DoAsync2()
{
await Task.Delay(5000);
return 5000;
}
private async void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "";
var doAsync1 = DoAysnc1();
var doAsync2 = DoAysnc2();
var async1 = await doAsync1;
var async2 = await doAsync2;
this.textBox1.Text = $"{async1} & {async2}";
}
After 5 seconds the result in the TextBox is "3000 & 5000".
But when I modify button1_Click like this:
private async void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "";
var async1 = await DoAysnc1();
var async2 = await DoAysnc2();
this.textBox1.Text = $"{async1} & {async2}";
}
the result is the same but it takes 8 sec.
Why second version of button1_Click act as synchronous?
Below is the explanation of the difference:
this.textBox1.Text = "";
var doAsync1 = DoAysnc1(); // <--- Run DoAsync1
var doAsync2 = DoAysnc2(); // <--- Run DoAsync2
var async1 = await doAsync1; // <--- wait for DoAsync1 to finish
var async2 = await doAsync2; //<--- wait for DoAsync2 to finish
this.textBox1.Text = $"{async1} & {async2}";
While:
this.textBox1.Text = "";
var async1 = await DoAysnc1(); // <-- Run DoAsync1 and wait for it to be done
var async2 = await DoAysnc2(); // <-- Run DoAsync2 and wait for it to be done
this.textBox1.Text = $"{async1} & {async2}";
So in the first version, both tasks are running at the same time. While in the second version you never run the second task until the first is Completed.
I think reading this Article is going to be a big plus for your knowledge.
With some comments to make what actually happens more clear:
// Starts DoAsync1 asynchronously
var doAsync1 = DoAysnc1();
// Starts DoAsync2 asynchronously
var doAsync2 = DoAysnc2();
// From now on, both task are executing
// Wait for DoAsync1 completion
var async1 = await doAsync1;
// Wait for DoAsync2 completion
var async2 = await doAsync2;
For the second case:
// Starts DoAsync1 asynchronously, and wait for the task completion (3s)
var async1 = await DoAysnc1();
// Starts DoAsync2 asynchronously, and wait for the task completion (5s)
var async2 = await DoAysnc2();
DoAsync1 and DoAsync2 return awaitable Task-objects. If you await them one by one then they are executed one after another (first wait for the first one to finish, then for the second one). In order to run them in parallel you can simply create the Task-objects first and wait for their results with Task.WhenAll.
private async void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "";
var task1 = DoAysnc1();
var task2 = DoAysnc2();
await Task.WhenAll(task1, task2)
this.textBox1.Text = $"{task1.Result} & {task2.Result}";
}

Categories