I'm re-writing an application that's currently deployed in a factory running a 3D imager and software, mounted to and communicating with an industrial robot, and communicating with a database. The application ties all 3 systems together for dimensional inspection of parts coming off a process. Although it works, it is not easily maintained, and the UI is unresponsive during the measuring process.
I have simplified the app greatly and moved to a MVVM pattern, but I need to improve overall cycle time and keep the UI responsive and smooth.
Every time I call on the async method
Write(Tag tag, string value) I'm getting a new task running along with all of the previous tasks re-starting again.
I don't understand how to properly Dispose of a Task once it's completed?
NOTE: "
Properties.Settings.Default.PlcBypass" is currently set to True until I get this debugged and can try it out with the PLC. (It's too risky when playing around with an industrial robot in production)
Also I'm checking for
tag.Name == "Control" because there are other Tag types that will have to be handled differently.
The Tag class is really just an INotifyable datatype with 3 properties
Here is the async code:
public async void Write(Tag tag, string value)
{
tag.Value = (await Task.Run(() => ToPlc(tag, value))).ToString();
}
private int ToPlc(Tag tag, string value)
{
Thread.Sleep(3000);
int result = int.Parse(value);
try
{
_plc.Write(tag.TagAddress, value);
}
catch (Exception ex)
{
if (tag.Name == "Control" && Properties.Settings.Default.PlcBypass)
{
if (result < 11)
{
result++;
}
else result = 0;
}
else throw new Exception(string.Format("WRITE FAILED FOR {0}: {1}", tag.Name, ex.Message));
}
return result;
}
Again, the Tasks are running asynchronously, but I'm getting a new task every time I call the async method, and all of the old tasks resume, all in parallel, each sending different values to the PLC (robot)
What I have tried:
I've tried various ways to figure this out, even to the point of writing an application just to "play with" asynchronous Tasks.
The code below is as simple as I think you can get but still include CancellationTokens and progress reporting. It was also modified to keep count of the duplicate tasks.
MainWindow.xaml:
<Window x:Class="Task_basedAsynchronousPatternTAP.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Task_basedAsynchronousPatternTAP"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical" Margin="10,20,10,10">
<StackPanel Orientation="Horizontal">
<TextBlock Name="MyClock" Text="CurrentTime" FontSize="20" Width="240" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBox Name="TaskLengthValue" Width="90" FontSize="20" TextAlignment="Center" Text="500" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button x:Name="buttonStart" Content="Start" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="buttonStart_Click"/>
<Button x:Name="buttonStop" Content="Stop" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="buttonStop_Click"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock x:Name="textBlock1" TextWrapping="Wrap" FontSize="60" Width="340" Height="80" Text="TextBlock" TextAlignment="Center"/>
</StackPanel>
<Label x:Name="label1" Content="Label" HorizontalContentAlignment="Center" FontSize="30"/>
<Label x:Name="labelResult" Content="Result" HorizontalContentAlignment="Center" FontSize="30"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace Task_basedAsynchronousPatternTAP
{
public partial class MainWindow : Window
{
private CancellationTokenSource ts;
private readonly SynchronizationContext synchronizationContext;
public string CurrentTime { get; set; }
public MainWindow()
{
InitializeComponent();
textBlock1.Text = "-2";
synchronizationContext = SynchronizationContext.Current;
DispatcherTimer timer = new DispatcherTimer(new TimeSpan(0, 0, 0, 0, 25), DispatcherPriority.Normal, delegate
{
this.MyClock.Text = DateTime.Now.ToString("HH:mm:ss.fff");
}, this.Dispatcher);
}
private DateTime previousTime = DateTime.Now;
private async void buttonStart_Click(object sender, RoutedEventArgs e)
{
labelResult.Content = "RUNNING";
ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
textBlock1.Text = "1";
int v = int.Parse(TaskLengthValue.Text);
var progress = new Progress<MyTaskProgressReport>();
progress.ProgressChanged += (o, report) =>
{
textBlock1.Text = report.TotalProgressAmount.ToString();
if (report.TotalProgressAmount % 10 == 0)
{
label1.Content = report.CurrentProgressMessage;
}
report = null;
};
int result = await LongRunningOperationAsync(ct, v, progress);
if (ts.IsCancellationRequested)
{
labelResult.Content = string.Format("RESULT: Stopped At {0} Ticks", result.ToString());
}
else
{
labelResult.Content = string.Format("RESULT: Completed!", result.ToString());
}
}
Task<int> LongRunningOperationAsync(CancellationToken ct, int v, Progress<MyTaskProgressReport> p)
{
LengthyOperation lo = new LengthyOperation(ct, p);
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
Task.Run(() =>
{
tcs.SetResult(lo.Start(v));
});
return tcs.Task;
}
private void buttonStop_Click(object sender, RoutedEventArgs e)
{
ts.Cancel();
}
}
}
LengthyOperation.cs: UPDATED ON MAY 19th Many Thanks to G3Coder :
Quote:
In the LengthyOperation class you have a static timer, and in the ctor you add elapsed events (+=) to that single static timer. So, when you enable the timer for subsequent LengthyOperations in the ctor, all events are still attached and then trigger, resulting in the confusion on the front end. I suggest you make the timer a non-static private field of the LengthyOperation class and test.
New Code: (old code further down for reference)
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Task_basedAsynchronousPatternTAP
{
class LengthyOperation
{
private CancellationToken ct;
private string taskID;
public int Ticks { get; set; }
IProgress<MyTaskProgressReport> progress;
public LengthyOperation(CancellationToken _ct, IProgress<MyTaskProgressReport> _pgrs)
{
ct = _ct;
progress = _pgrs;
taskID = Task.CurrentId.ToString();
}
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Ticks++;
MyTaskProgressReport statusReport = new MyTaskProgressReport { Name = "LengthyOperation", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) };
progress.Report(statusReport);
statusReport = null;
}
public int Start(int l)
{
Ticks = 0;
progress.Report(new MyTaskProgressReport { Name = "LengthyOperationStarted", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
System.Timers.Timer _timer = new System.Timers.Timer();
_timer.Interval = 150;
_timer.Elapsed += _timer_Elapsed;
_timer.Enabled = true;
taskID = Task.CurrentId.ToString();
while (Ticks < l)
{
if (ct.IsCancellationRequested) break;
Thread.Sleep(10);
}
_timer.Enabled = false;
if (ct.IsCancellationRequested)
{
progress.Report(new MyTaskProgressReport { Name = "LengthyOperationCancled", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
}
else
{
progress.Report(new MyTaskProgressReport { Name = "LengthyOperationCompleted", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
}
return Ticks;
}
}
public class MyTaskProgressReport
{
public string Name { get; set; }
public string ThisTaskID { get; set; }
public int CurrentProgressAmount { get; set ; }
public int TotalProgressAmount { get; set; }
public string CurrentProgressMessage { get; set; }
}
}
Old Code:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Task_basedAsynchronousPatternTAP
{
class LengthyOperation
{
private static System.Timers.Timer _timer = new System.Timers.Timer();
private CancellationToken ct;
private string taskID;
public int Ticks { get; set; }
IProgress<MyTaskProgressReport> progress;
public LengthyOperation(CancellationToken _ct, IProgress<MyTaskProgressReport> _pgrs)
{
ct = _ct;
progress = _pgrs;
_timer.Interval = 150;
_timer.Elapsed += _timer_Elapsed;
taskID = Task.CurrentId.ToString();
}
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Ticks++;
MyTaskProgressReport statusReport = new MyTaskProgressReport { Name = "LengthyOperation", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) };
progress.Report(statusReport);
statusReport = null;
}
public int Start(int l)
{
_timer.Enabled = true;
taskID = Task.CurrentId.ToString();
while (Ticks < l)
{
if (ct.IsCancellationRequested) break;
Thread.Sleep(10);
}
_timer.Enabled = false;
if (ct.IsCancellationRequested)
{
progress.Report(new MyTaskProgressReport { Name = "LengthyOperationCancled", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
}
else
{
progress.Report(new MyTaskProgressReport { Name = "LengthyOperationCompleted", ThisTaskID = taskID, CurrentProgressAmount = 1, TotalProgressAmount = Ticks, CurrentProgressMessage = string.Format("Operation Step: {0}, TaskID: {1}", Ticks, taskID) });
}
return Ticks;
}
}
public class MyTaskProgressReport
{
public string Name { get; set; }
public string ThisTaskID { get; set; }
public int CurrentProgressAmount { get; set ; }
public int TotalProgressAmount { get; set; }
public string CurrentProgressMessage { get; set; }
}
}