I have this event handler in asp.net page:
protected void SetDescPoint(object sender, EventArgs e)
{
try
{
ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(foo));
}
catch (Exception)
{ }
}
private void foo(object a)
{
try
{
System.Diagnostics.Debug.WriteLine("Start - " + DateTime.Now.ToString("h:mm:ss tt"));
TimeSpan minutes = TimeSpan.FromMinutes(10);
System.Threading.Thread.Sleep(minutes);
string path = UniquePath();
File.Delete(path);
System.Diagnostics.Debug.WriteLine("Deleted - " + DateTime.Now.ToString("h:mm:ss tt"));
}
catch (Exception ex)
{
Debug.WriteLine("EXCEPTION - " + ex.Message);
}
}
SetDescPoint is event handler and fired in response to client event.As you can see the function foo has Thread.Sleep(10minutes) there is might be situation when event handler fired in time interval less than 10 minutes, so in that situation I need to delete current task(foo()) in pool threads.
Any Idea how can I implement it?
Rewrite your code to use Task.Delay. This has two benefits: you're not holding up a thread anymore (as Task.Delay uses a timer internally), and you can use a cancellation token to cancel the wait:
protected CancellationTokenSource CancellationToken { get; private set; }
protected void SetDescPoint(object sender, EventArgs e)
{
try
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
this.CancellationToken = new CancellationTokenSource();
Task.Run(() => foo(this.CancellationToken.Token), this.CancellationToken.Token);
}
catch (Exception)
{ }
}
private async Task foo(CancellationToken token)
{
try
{
System.Diagnostics.Debug.WriteLine("Start - " + DateTime.Now.ToString("h:mm:ss tt"));
TimeSpan minutes = TimeSpan.FromMinutes(10);
await Task.Delay(minutes, token);
string path = UniquePath();
File.Delete(path);
System.Diagnostics.Debug.WriteLine("Deleted - " + DateTime.Now.ToString("h:mm:ss tt"));
}
catch (Exception ex)
{
Debug.WriteLine("EXCEPTION - " + ex.Message);
}
}
Whenever you want to cancel your task, just call CancellationToken.Cancel()
A rather simplified way of dealing with this would be to keep track of a CancellationTokenSource for each path your create (I'm not sure if there a multiple paths or a simple path, but just in case), and then look it up once the event fires again.
Using this with Task.Delay, which asynchronously yields control in a non-blocking fashion, can achieve what you want:
private ConcurrentDictionary<string, CancellationTokenSource> pathsToTokens =
new ConcurrentDictionary<string, CancellationTokenSource>();
protected async void SetDescPointAsync(object sender, EventArgs e)
{
CancellationTokenSource existingTokenSource;
var path = UniquePath();
if (pathsToTokens.TryGetValue(path, out existingTokenSource))
{
existingTokenSource.Cancel();
}
var cancellationTokenSource = new CancellationTokenSource();
pathsToTokens.AddOrUpdate(path, cancellationTokenSource,
(pathToFile, token) => cancellationTokenSource);
try
{
await Task.Delay(TimeSpan.FromMinutes(10), cancellationTokenSource.Token)
.ConfigureAwait(false);
}
catch (TaskCanceledException tce)
{
// Token was cancelled, do something?
}
Foo(path);
pathsToTokens.TryRemove(path, out cancellationTokenSource);
}
private void Foo(string path)
{
try
{
File.Delete(path);
Debug.WriteLine("Deleted - " + DateTime.Now.ToString("h:mm:ss tt"));
}
catch (Exception ex)
{
Debug.WriteLine("EXCEPTION - " + ex.Message);
}
}
What happens with this code is that for each path you create, you allocate a new CancellationTokenSource. Every time the event is triggered, you check for an existing token. If it is in place, that means the event still hasn't finished, and you want to cancel it. Then, you asynchronously wait the amount of time you need. Note that Task.Delay is wrapped in a try-catch as calling CancellationTokenSource.Cancel will cause it to throw an exception once completed.
Related
Good day.
I'm having a problem exiting a task with the cancellation token.
My program freezes when I get to the token2.ThrowIfCancellationRequested();.
Following it with the breakpoints is shows that the token2 is cancelled, but the program doesn't revert back to the previous sub routine where I try and catch
try
{
Task.Run(() => SendData_DoWork(_tokenSource3));
}
catch (OperationCanceledException ex)
{
SetText("Communivation error with device");
SetText("");
}
finally
{
token.Dispose();
}
}//comms routine
//send Meter Address to communicate to meter
private void SendData_DoWork(CancellationTokenSource token)
{
var token2 = token.Token;
var _tokenSource4 = new CancellationTokenSource();
try
{
timer.Interval = 10000;
timer.Start();
timer.Elapsed += OnTimerElapsed;
NetworkStream stream = client.GetStream();
SerialConverter serialConverter = new SerialConverter();
Thread.Sleep(1000);
string newtext = null;
newtext = $"/?{address}!\r\n";
SetText("TX: " + newtext);
byte[] newData = stringSend(newtext);
stream.Write(newData, 0, newData.Length);
Thread.Sleep(50);
byte[] message = new byte[23];
int byteRead;
while (true)
{
byteRead = 0;
try
{
byteRead = stream.Read(message, 0, 23);
if (message[0] == (char)0x15)
{
token.Cancel();
}
}
catch
{
token.Cancel();
}
if ((byteRead == 0))
{
token.Cancel();
}
timer.Stop();
timer.Dispose();
ASCIIEncoding encoder = new ASCIIEncoding();
string newresponse = encoder.GetString(serialConverter.convertFromSerial(message));
SetText("RX: " + newresponse);
if (newresponse[0].ToString() == SOH)
{
token.Cancel();
}
if (newresponse != null)
{
/* NEXT SUB ROUTINE*/
}
else { break; }
}//while looop
}//try
catch (Exception ex)
{
token.Cancel();
}
if (token2.IsCancellationRequested)
{
timer.Stop();
timer.Dispose();
token2.ThrowIfCancellationRequested();
}
}//sendData subroutine
You are launching a Task, and ignoring the result; the only time Task.Run would throw is if the task-method is invalid, or enqueuing the operation itself failed. If you want to know how SendData_DoWork ended, you'll need to actually check the result of the task, by capturing the result of Task.Run and awaiting it (preferably asynchronously, although if we're talking async, SendData_DoWork should probably also be async and return a Task).
Your catch/finally will probably be exited long before SendData_DoWork even starts - again: Task.Run just takes the time required to validate and enqueue the operation; not wait for it to happen.
I think you have missunderstood how cancellation tokens are supposed to work. Your work method should take a CancellationToken, not a CancellationTokenSource. And it should call ThrowIfCancellationRequested inside the loop, not after. I would suspect that you would get some issues with multiple cancel calls to the same cancellation token.
Typically you would use a pattern something like like this:
public void MyCancelButtonHandler(...) => cts.Cancel();
public async void MyButtonHandler(...){
try{
cts = new CancellationTokenSource(); // update shared field
await Task.Run(() => MyBackgroundWork(cts.Token));
}
catch(OperationCancelledException){} // Ignore
catch(Exception){} // handle other exceptions
}
private void MyBackgroundWork(CancellationToken cancel){
while(...){
cancel.ThrowIfCancellationRequested();
// Do actual work
}
}
So in my particular case it seems like changing the sub-routines from private async void ... to private async Task fixes the particular issue that I'm having.
I would like to know if the code I produced is good practice and does not produce leaks, I have more than 7000 objects Participant which I will push individually and Handle the Response to save the "external" id in our database. First I use the Parallel ForEach on the list pPartcipant:
Parallel.ForEach(pParticipant, participant =>
{
try
{
//Create
if (participant.id == null)
{
ExecuteRequestCreate(res, participant);
}
else
{//Update
ExecuteRequestUpdate(res, participant);
}
}
catch (Exception ex)
{
LogHelper.Log("Fail Parallel ", ex);
}
});
Then I do a classic (not async request), but after I need to "handle" the response (print in the console, save in a text file in async mode, and Update in my database)
private async void ExecuteRequestCreate(Uri pRes, ParticipantDo pParticipant)
{
try
{
var request = SetRequest(pParticipant);
//lTaskAll.Add(Task.Run(() => { ExecuteAll(request, pRes, pParticipant); }));
//Task.Run(() => ExecuteAll(request, pRes, pParticipant));
var result = RestExecute(request, pRes);
await HandleResult(result, pParticipant);
//lTaskHandle.Add(Task.Run(() => { HandleResult(result, pParticipant); }));
}
catch (Exception e)
{
lTaskLog.Add(LogHelper.Log(e.Message + " " + e.InnerException));
}
}
Should I run a new task for handeling the result (as commented) ? Will it improve the performance ?
In comment you can see that I created a list of tasks so I can wait all at the end (tasklog is all my task to write in a textfile) :
int nbtask = lTaskHandle.Count;
try
{
Task.WhenAll(lTaskHandle).Wait();
Task.WhenAll(lTaskLog).Wait();
}
catch (Exception ex)
{
LogHelper.Log("Fail on API calls tasks", ex);
}
I don't have any interface it is a console program.
I would like to know if the code I produced is good practice
No; you should avoid async void and also avoid Parallel for async work.
Here's a similar top-level method that uses asynchronous concurrency instead of Parallel:
var tasks = pParticipant
.Select(participant =>
{
try
{
//Create
if (participant.id == null)
{
await ExecuteRequestCreateAsync(res, participant);
}
else
{//Update
await ExecuteRequestUpdateAsync(res, participant);
}
}
catch (Exception ex)
{
LogHelper.Log("Fail Parallel ", ex);
}
})
.ToList();
await Task.WhenAll(tasks);
And your work methods should be async Task instead of async void:
private async Task ExecuteRequestCreateAsync(Uri pRes, ParticipantDo pParticipant)
{
try
{
var request = SetRequest(pParticipant);
var result = await RestExecuteAsync(request, pRes);
await HandleResult(result, pParticipant);
}
catch (Exception e)
{
LogHelper.Log(e.Message + " " + e.InnerException);
}
}
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.
I have a timer using which i am calling a method - {OnElapsedTime} And this should responsible for database access and updation for every interval of time.
protected override void OnStart(string[] args)
{
try
{
ServiceLogFile("Service is started at " + DateTime.Now);
timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
timer.Interval = Int32.Parse(ConfigurationManager.AppSettings["tracktime"]); //number in miliseconds
timer.Enabled = true;
}
catch(Exception ex)
{
ServiceLogFile("Error in {OnStart} :" + ex.ToString());
}
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
try
{
ServiceLogFile("Check for Database values - " + DateTime.Now);
th = new Thread(new ThreadStart(Autocancellation));
int ThreadID = Thread.CurrentThread.ManagedThreadId;
ServiceLogFile("Thread ID = " + ThreadID);
th.Start();
}
catch (Exception ex)
{
ServiceLogFile("Error in {OnElapsedTime} :" + ex.ToString());
}
}
public void Autocancellation()
{
try
{
lock (this)
{
//connection to database
//select Query and update
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message());
}
}
If we can see the above code - I am creating a new thread for every OnElapsedTime call.
Please help me with creating a single thread outside of this method or anywhere and using the same instance inside {OnElapsedTime} whenever this method is being called (In my case - 5 seconds)
Assuming
that we properly implement cooperative cancellation (otherwise there are no reliable ways to stop the currently running job)
And we are not forced to use threads - https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl
And we are not required to run exactly once per 5 seconds (to accommodate the cooperative cancellation from bullet 1) - C# Timer elapsed one instance at a time
the simplest (or rather the simplest without any additional libraries) way would be to use Task, TaskCancellationSource and a timer with AutoReset = false
public class Service
{
private Task _job;
private CancellationTokenSource _cancellationTokenSource;
private readonly System.Timers.Timer _timer;
public Service()
{
_timer = new System.Timers.Timer();
{
_timer.AutoReset = false;
_timer.Interval = TimeSpan.FromSeconds(5).TotalMilliseconds;
_timer.Elapsed += OnElapsedTime;
}
}
public void Start()
{
Console.WriteLine("Starting service");
_timer.Start();
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
Console.WriteLine("OnElapsedTime");
if (_job != null)
{
CancelAndWaitForPreviousJob();
}
Console.WriteLine("Starting new job");
_cancellationTokenSource = new CancellationTokenSource();
_job = Task.Run(
() => ExecuteJob(_cancellationTokenSource.Token),
_cancellationTokenSource.Token);
_timer.Start();
}
private void CancelAndWaitForPreviousJob()
{
_cancellationTokenSource.Cancel();
try
{
Console.WriteLine("Waiting for job to complete");
_job.Wait(
millisecondsTimeout: 5000); // Add additional timeout handling?
}
catch (OperationCanceledException canceledExc)
{
Console.WriteLine($"Cancelled the execution: {canceledExc}");
}
catch (Exception exc)
{
Console.WriteLine($"Some unexpected exception occurred - ignoring: {exc}");
}
}
private void ExecuteJob(CancellationToken cancellationToken)
{
Console.WriteLine("ExecuteJob start");
try
{
for (var i = 0; i < 10; i++)
{
Console.WriteLine($"Job loop Iteration {i}");
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Cancellation requested - ending ExecuteJob");
return;
}
Thread.Sleep(1000);
}
}
finally
{
Console.WriteLine("ExecuteJob end");
}
}
}
While it is a working solution you may be interested in Quartz.net - it has only a moderate learning curve and it is designed exactly for such scenarios.
P.S.: Also it seems that your application is a service based on https://learn.microsoft.com/en-Us/dotnet/api/system.serviceprocess.servicebase?view=netframework-4.8. In such a case you may be interested in topshelf - it greatly simplifies a lot of things related to services.
I have this code in a button click
private async void btnStart_Click(object sender, EventArgs e)
{
Msg.Clear();
stopWatch.Reset();
timer.Start();
stopWatch.Start();
lblTime.Text = stopWatch.Elapsed.TotalSeconds.ToString("#");
progressBar.MarqueeAnimationSpeed = 30;
try
{
await Task.Factory.StartNew(() =>
{
try
{
Reprocess();
}
catch (Exception ex)
{
Msg.Add(new clsMSG(ex.Message, "Error", DateTime.Now));
timer.Stop();
stopWatch.Stop();
throw;
}
});
}
catch
{
throw;
}
}
and this on the Reprocess method
private void Reprocess()
{
try
{
clsReprocess reprocess = new clsReprocess(tbBD.Text, dtpStart.Value, 50000);
reprocess.Start(reprocess.BD);
}
catch
{
throw;
}
}
when the Reprocess method fails, the Task goes to catch, but the throw fails (the throw inside catch (Exception ex)) and the UI blocks until the reprocess.Start method is completed.
I have two questions:
First: How can I catch the throw in the catch of my button?
Second: How can I prevent the UI blocks?
I hope you can understand me, sorry for my bad english.
You should not use Task.Factory.StartNew; Task.Run is both safer and shorter to write.
Also, you can only access UI controls from the UI thread. This may be the cause of the problems you're seeing, if Msg is data-bound to the UI. Even if it's not, you don't want to access unprotected collections (e.g., List<clsMSG>) from multiple threads.
Applying both of these guidelines reduces the code to:
private async void btnStart_Click(object sender, EventArgs e)
{
Msg.Clear();
stopWatch.Reset();
timer.Start();
stopWatch.Start();
lblTime.Text = stopWatch.Elapsed.TotalSeconds.ToString("#");
progressBar.MarqueeAnimationSpeed = 30;
try
{
await Task.Run(() => Reprocess());
}
catch (Exception ex)
{
Msg.Add(new clsMSG(ex.Message, "Error", DateTime.Now));
timer.Stop();
stopWatch.Stop();
throw;
}
}
If Reprocess throws an exception, that exception will be placed on the task returned from Task.Run. When your code awaits that task, that exception is re-raised and caught in the catch. At the end of the catch, the code will re-raise that exception (throw;).