Click here to Skip to main content
15,887,135 members
Please Sign up or sign in to vote.
2.00/5 (3 votes)
See more:
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
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// Progress Reporting:  https://social.technet.microsoft.com/wiki/contents/articles/19020.progress-of-a-task-in-c.aspx
    /// Codeproject question post: https://www.codeproject.com/Questions/1185488/TAP-my-task-has-completed-how-do-I-remove-it
    /// 
    /// </summary>
    public partial class MainWindow : Window
    {
        //private CancellationToken ct;
        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);
            //create a task completion source
            //the type of the result value must be the same
            //as the type in the returning Task
            TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
            Task.Run(() =>
            {
                //set the result to TaskCompletionSource
                tcs.SetResult(lo.Start(v));
            });
            //return the Task
            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
    {
        //task name
        public string Name { get; set; }
        // TaskID
        public string ThisTaskID { get; set; }
        //current progress
        public int CurrentProgressAmount { get; set ; }
        //total progress
        public int TotalProgressAmount { get; set; }
        //some message to pass to the UI of current progress
        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
    {
        //task name
        public string Name { get; set; }
        // TaskID
        public string ThisTaskID { get; set; }
        //current progress
        public int CurrentProgressAmount { get; set ; }
        //total progress
        public int TotalProgressAmount { get; set; }
        //some message to pass to the UI of current progress
        public string CurrentProgressMessage { get; set; }
    }
}
Posted
Updated 19-May-17 3:01am
v8
Comments
Member 11577008 17-May-17 17:17pm    
I forgot to explain this async method gets called from another class which handles the 3D imaging.... currently that class waits on "continue" input from me, so I know that part is currently synchronous, and the two are not running over the tops of each other.
CHill60 17-May-17 17:36pm    
One of the reasons you didn't get any responses to your previous post is because you removed it from the list of unanswered posts by posting a solution! If you have extra information to add to your post use the Improve question link (becomes visible when you hover a mouse over your post, or click/tap within your post)
Member 11577008 18-May-17 7:25am    
Thanks, I realized it was a mistake after I posted it, and then figured out where the comment link was. I didn't realize though that my post was no longer visible. I've been reading codeproject for years, but as you've probably guessed, these are my first requests for help.
CHill60 18-May-17 7:34am    
It's still "visible" but if anyone uses the "Unanswered Questions" list then they wouldn't see it. Many people who use the "All Questions" list would still skip over it because it has a "solution"
I see people have started downvoting your post - I suggest that you edit this post using the Improve question link to move any information from your other post into this one, remove the word "Urgent" from your title and the links to your other post from this one. Then delete your other post - unless someone has answered it of course! If you are not aware, when you hover over your question, or tap into it, a large red X becomes visible in the bottom right of the post - that's how you delete it (it's not always visible)
CHill60 17-May-17 17:40pm    
Oh and avoid the use of the word "urgent" - it might be urgent to you, but we're all volunteers here. There are also some "bolshie" types that will ignore your post because you use the word urgent. Just so you know.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900