EventHandler does not get triggered in .NET Core worker service app

Viewed 449

I am trying to create OPCDA C# client with worker service template in .NET Core 3.1.

My goal is to read PLC tag value when DataChange event is triggered by OPC DA. The sample code I have created does the job properly in Console App (.NET Core 3.1) but when I utilize same code in Worker Service App (.NET Core 3.1), I found that it connects to OPC DA Server properly but the DataChange event does not call the method. I tried adding this code in

public override Task StartAsync(CancellationToken cancellationToken) {}

as well as

protected override async Task ExecuteAsync(CancellationToken stoppingToken) {}

but it does not trigger the event and there is no error.

This is my sample code:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Opc.Da;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace SampleWorkerService
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;
        private readonly IConfiguration _config;

        public Opc.URL url;
        private Opc.Da.Server server;
        private OpcCom.Factory fact = new OpcCom.Factory();
        private Opc.Da.Subscription groupRead;
        private Opc.Da.SubscriptionState groupState;
        private List<Item> itemsList = new List<Item>();
        string opcTopicName = "TopicName";

        public Worker(ILogger<Worker> logger, IConfiguration configuration)
        {
            _logger = logger;
            _config = configuration;
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
                url = new Opc.URL("opcda://localhost/OPC Server");
                server = new Opc.Da.Server(fact, null);
                server.Connect(url, new Opc.ConnectData(new System.Net.NetworkCredential())); 
                //On Debug Server.IsConnected = true meaning server is connected properly
                groupState = new Opc.Da.SubscriptionState();
                groupState.Name = "Group";
                groupState.UpdateRate = 1000;
                groupState.Active = true;
                groupRead = (Opc.Da.Subscription)server.CreateSubscription(groupState);
                groupRead.DataChanged += null;

                Item item = new Item();

                for (int i = 0; i < listTagNamesFrmconfigFile.Count; i++)
                {
                    item = new Item();
                    item.ItemName = $"{opcTopicName}{listTagNamesFrmconfigFile[i].Name}";
                    itemsList.Add(item);
                }

                groupRead.AddItems(itemsList.ToArray()); // On Debug, Item list is added properly

          //groupRead.DataChanged += 
                       new Opc.Da.DataChangedEventHandler(GroupRead_DataChanged); //tried this
          /await Task.Run(() => groupRead.DataChanged += GroupRead_DataChanged); //tried this
          groupRead.DataChanged += GroupRead_DataChanged; 
// This line does not trigger method GroupRead_DataChanged which reads Item Values, THIS IS THE ISSUE (Same Code Works good in Console App)

            return base.StartAsync(cancellationToken);
        }

protected void GroupRead_DataChanged(object subscriptionHandle, object requestHandle, ItemValueResult[] values)
        {
            foreach (ItemValueResult itemValue in values)
            {
                objPLCTagInfo readPLCTagInfo = new objPLCTagInfo();
                readPLCTagInfo.Value = itemValue.Value;
            }
        }

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
             // I tried below commented code, the opcMethods.OPCRead has same code above but does 
                not trigger "groupRead.DataChanged"
            
             //OPCMethods opcMethods = new OPCMethods();

            //while (!stoppingToken.IsCancellationRequested)
            //{
            //    //_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

            //    opcMethods.OPCRead(_config);

            //    await Task.Delay(1000, stoppingToken).ConfigureAwait(false);
            //}

        // Following code also doesnot trigger "groupRead.DataChanged"
          
            groupRead.DataChanged += (object subscriptionHandle, object requestHandle, 
                                           ItemValueResult[] values) =>
            {
                Console.WriteLine("Event Triggered");
            };

            // await Task.CompletedTask;

            await Task.Delay(1000, stoppingToken).ConfigureAwait(false);
        }

public override Task StopAsync(CancellationToken cancellationToken)
        {
            return StopAsync(cancellationToken);
        }

    }
}

Above Code works good with Console App. The event groupRead.DataChange does not trigger the method "GroupRead_DataChanged" to read the data, there is no error/exception occurs:

This Code Line and following method reads item values, which I am trying to make it work

groupRead.DataChanged += GroupRead_DataChanged;

protected void GroupRead_DataChanged(object subscriptionHandle, object requestHandle, ItemValueResult[] values)
    {
        //logic here to read item values
        foreach (ItemValueResult itemValue in values)
            { ... }
    }

I hope I have explained the issue I am trying to address.

I appreciate your time and solution provided in advance !

1 Answers

You need to change your approach for this to work. The way you are using the backgroundservice means that it processes straight through and gets disposed before it has chance to use the EventHandler.

The easiest way to tackle this is to create a "looped" Background Service so that it stays running and allows the EventHandler to work. For an example of how to implement this check out this link: https://medium.com/@daniel.sagita/backgroundservice-for-a-long-running-work-3debe8f8d25b

TLDR:

public class WorkerHostedService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stopToken)
    {
      //Do your preparation (e.g. Start code) here
      while (!stopToken.IsCancellationRequested)
      {
        ## YOUR-CODE-HERE ##
      }
      //Do your cleanup (e.g. Stop code) here
    }
}
Related