Update UI with continues async Task through object list does not perform as desired

80 views Asked by At

In my application I have a communication driver that communicates with a PLC, this works fine. I would like to manage all value update requests, for this i created the ValueListener object who registers itself in a ValueListenManager, this also works fine. In my synchronous tests UI performance was sluggish as expected. Perfect now let's make it async.

I tried different approaches like backgroundworker, timer and Task.Run this is the best i could do so far, it works and if i printscreen the tasks ManagedThreadId threads ID's are different (what tells me it should be async)

But UI response performance does not really seems to improve, this tells me it is not really async. Can anyone help me with this?

The only weird thing i found so far is that ValueListener's can be added to the Managers ListenList while the CheckPlcValuesTask already updates, this also suggests me that it is not really async, what am i doing wrong? (i'm new to async programming so any help or tips are appreciated)

EDIT: code does now compile

  public class CommManager
     {
         public static class ValueListenManager
        {
            static private List<ValueListener> ListenList = new List<ValueListener>();

            public async static void Start()
            {                    
                var UpdateChangedValues = new Progress<List<ValueListener>>(UiUpdateList =>
                {
                    foreach (ValueListener item in UiUpdateList)
                        item.TriggerChange();
                });
                await Task.Run(() => CheckPlcValuesTask(UpdateChangedValues));
            }


            private static Task CheckPlcValuesTask(IProgress<List<ValueListener>> progressList)
            {
                List<ValueListener> returnList = new List<ValueListener>();
                while (true)
                {
                    if (returnList.Count != 0)
                        returnList.Clear();

                    Parallel.ForEach(ListenList, (CurrentItem) =>  // ListenList is the static List declared in the top
                    {
                        if (CurrentItem.CheckValue())
                            returnList.Add(CurrentItem);
                    });

                    if (progressList != null)
                    {
                        progressList.Report(returnList);
                        Thread.Sleep(1000);
                    }
                }

            }

            static internal void RegisterLister(ValueListener Listner)
            {
                ListenList.Add(Listner);
            }

            static internal void UnRegisterLister(ValueListener Listner)
            {
                ListenList.Remove(Listner);
            }
        }
    }
}


internal class ValueListener
{
    public event EventHandler<ValueChangeEventArgs> Changed;
    public string FullPath { get; }
    public object Value { get; private set; }
    private object oldObjectValue;

    internal ValueListener(string Path) // update prio is a enum
    {
        this.FullPath = Path;
        CommManager.ValueListenManager.RegisterLister(this);   // here i register into the ListenList above
    }

    ~ValueListener()
    {
        CommManager.ValueListenManager.UnRegisterLister(this);  // de-register after destruction
    }

    // Check for value changes
    public bool CheckValue()
    {
        var SmallTest = new Random();
        Value = SmallTest.Next(1, 5);
        if (Value != null && !Value.Equals(oldObjectValue))
            return true;

        return false;
    }


    // Check for value change, here is where i trigger updates on the UI
    public void TriggerChange()
    {
        if (Changed != null)
            Changed.Invoke(this, new ValueChangeEventArgs(Value, oldObjectValue));
        oldObjectValue = Value;
    }
}


public class ValueChangeEventArgs : EventArgs
{
    public ValueChangeEventArgs(object Value, object OldValue)
    { }
}
1

There are 1 answers

0
Timothy Thompson On

This should work alright for you. It takes advantage of Microsoft TPL Dataflow, which is available as a nuget package.

public static partial class CommManager
{
    public static class ValueListenManager
    {
        public static event Action<object> NewValueAvailable;

        private static Thread s_valueQueryThread;
        private static ActionBlock<object> s_newValueActionBlock;
        private static bool s_shouldRun;

        private static Random s_valueGenerator; //for testing purposes

        public static void Start()
        {
            //These flow options will make sure your action block executes on the UI thread. Make sure you call start on the UI Thread!
            var flowOptions = new ExecutionDataflowBlockOptions(){ TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() };

            s_newValueActionBlock = new ActionBlock<object>(new Action<object>(OnNewValue), flowOptions);
            s_valueQueryThread = new Thread(CheckPlcValues);

            s_valueQueryThread.IsBackground = true;
            s_valueQueryThread.Start();
        }

        public static void Stop()
        {
            s_shouldRun = false;

            s_valueQueryThread.Join();
            s_valueQueryThread = null;

            s_newValueActionBlock.Complete();
            s_newValueActionBlock = null;
        }

        private static void OnNewValue(object value)
        {
            if(NewValueAvailable != null)
                NewValueAvailable(value);
        }

        private static void CheckPlcValues()
        {
            s_shouldRun = true;

            while(s_shouldRun)
            {
                var curPlcValue = s_valueGenerator.Next(1, 5);

                s_newValueActionBlock.Post(curPlcValue);

                Thread.Sleep(1000);
            }
        }
    }
}

internal class ValueListener
{
    public event EventHandler<ValueChangeEventArgs> ValueChanged;

    public string FullPath { get; private set; }
    public object Value { get; private set; }

    internal ValueListener(string path)
    {
        this.FullPath = path;
        CommManager.ValueListenManager.NewValueAvailable += CommManager_ValueListenManager_NewValueAvailable;
    }

    ~ValueListener()
    {
        CommManager.ValueListenManager.NewValueAvailable -= CommManager_ValueListenManager_NewValueAvailable;
    }

    void CommManager_ValueListenManager_NewValueAvailable(object newValue)
    {
        if(newValue != null && !newValue.Equals(Value))
        {
            var args = new ValueChangeEventArgs(newValue, Value);
            Value = newValue;

            if(ValueChanged != null)
                ValueChanged(this, args);
        }
    }
}

public class ValueChangeEventArgs : EventArgs
{
    public ValueChangeEventArgs(object Value, object OldValue) { }
}