Aamir
Aamir

Reputation: 1757

how to propagate some data to main process from TPL tasks while tasks are running

I have a situation where I create a list of long running tasks which monitors some system/network resources and then sends email, logs into a txt file, and calls a web service when some conditions are met. Then begins monitoring again. These tasks are created in a windows service and hence will be running all the time.

I want them to raise events or something to notify the parent class (which created them) and it will performs the 3 operations i mentioned above instead of each object in tasks doing it by itself.

And how can it be controlled that only a single task uses that parent class's method at a single time. As Email and a web service call is involved, so two concurrent requests may beak the code.

UPDATE

These Watchers are of three types, each implements the following interface.

public interface IWatcher
{
    void BeginWatch();        
}

Classes that implement are

//this watcher is responsible for watching over a sql query result
public class DBWatcher : IWatcher
{
    ....
    void BeginWatch()
    {
        //Here a timer is created which contiously checks the SQL query result.
        //And would Call SERVICE, send an EMAIL and LOG into a file

        Timer watchIterator = new Timer(this._intervalMinutes * 60000);
        watchIterator.Elapsed += new ElapsedEventHandler(_watchIterator_Elapsed);
        watchIterator.Start();
    }

    void _watchIterator_Elapsed(object sender, ElapsedEventArgs e)
    {
        //1. Check Query result
        //3. Call SERVICE, send an EMAIL and LOG into a file if result is not as was expected
        //I have done the work to this part!

        //And I can do the functions as follows .. it should be simple.
        //*********************
        //SendEmail();
        //LogIntoFile();
        //CallService();

        //But I want the above three methods to be present in one place so i dont have to replicate same functionality in different watcher.
        //One approach could be to create a seperate class and wrape the above mentioned functions in it, create an instance of that class here and call them.
        //Second option, which I am interested in but dont know how to do, is to have this functionality in the parent class which actually creates the tasks and have each watcher use it from HERE ...
    }
    ....
}


//this watcher is responsible for watching over Folder
public class FolderWatcher : IWatcher
{
    ....
    void BeginWatch()
    {
         ///Same as above
    }
    ....
}

First I create a List from an XML file. This can contain multiple instances of DBWatcher which will continously watch a different query result and FolderWatcher which will continously watch different Folders continously.

After the List is created, I call the following function that I call to create a separate Task. I call this function MANY times to create a different set of watchers.

    private void _createWatcherThread(IWatcher wat, CancellationTokenSource cancellationToken)
    {
        //This represents a watcher that will watch some specific area for any activities
        IWatcher watcher = wat.Copy();
        bool hasWatchBegin = false;
        try
        {
            //run forever
            for (;;)
            {
                //dispose the watcher and stop this thread if CANCEL token has been issued
                if (cancellationToken.IsCancellationRequested)
                {
                    ((IDisposable)watcher).Dispose();
                    break;
                }
                else if (!hasWatchBegin)
                {
                    //This method of a watcher class creates a timer. which will
                    //continously check the status after a few minutes... So its the 
                    //timer's elapsed method in Watcher object which will send the mail 
                    //& call service etc to update the admin of current status of the watcher.
                    //this will be called only once in a watcher!
                    watcher.BeginWatch();
                    hasWatchBegin = true;
                }
            }
        }
        catch (Exception ex)
        {
            //Watcher has thrown an exception. 
            //Again, do the following operations
            //*********************
            //SendEmail();
            //LogIntoFile();
            //CallService();
        }
    }

Upvotes: 0

Views: 190

Answers (2)

Servy
Servy

Reputation: 203820

The Progress class is just perfect for this task. It is a means of allowing a long running process to notify someone (usually the caller) of the current progress of that operation.

Progress<string> progress = new Progress<string>();
progress.ProgressChanged += (s, data) => Console.WriteLine(data);

for (int i = 0; i < 2; i++)
    Task.Run(() => DoWork(progress));

public static void DoWork(IProgress<string> progress)
{
    int i = 0;
    while (true)
    {
        Thread.Sleep(500);//placeholder for real work
        progress.Report(i++.ToString());
    }
}

If you have different types of information to report at different times then just pass in multiple IProgress instances to the worker method. (Or, if you are reporting the progress of several types of data at the same time wrap all of the data in a composite object.)

Also note that this is capable of handling the synchronization that you have said that you need. Progress instances, when created, capture the value of SynchronizationContext.Current at the time that it's created, and marshal all of the event handlers for the progress changed event into that sync context. So if your application will already have a context (i.e. a UI context from a desktop application) then you get that for free. If you don't have one (i.e. it's a console application) then you'll need to either manually synchronize the event handler with say a lock, or create your own SynchrnonizationContext to set as the current context.

Upvotes: 0

Chris
Chris

Reputation: 2895

Provided you make your email, logging & webservice calls threadsafe you can pass references to the code which sends to each of these sinks as a closure (Here's Jon Skeet's excellent explanation of c# closures) into the monitoring tasks. Here's an example where you need to launch multiple tasks:

...
void Email(string message){}

void Log(string message){}

void CallWebService(string message){}


void RunMonitoringTask()
{
    var task = Task.TaskFactory.StartNew(() =>
    {
        string message = Monitor();
        if( ShouldNotify(message))
           {
              Email(mesasge);
              Log(message);
              CallWebService(message);
           }
    }
)
}
...

EDIT

vs. an infinite monitor loop triggering tasks when necessary:

...
void Email(string message){}

void Log(string message){}

void CallWebService(string message){}


void Monitor()
{
    while(true)
    {
          string message = Monitor();
          if(ShouldNotify(message))
          { 
              var task = Task.TaskFactory.StartNew(() =>
              {
                  Email(mesasge);
                  Log(message);
                  CallWebService(message);
              }
         }
    }
)
}
...

As far as how to implement these classes, I'd recomend an approach where each of these sinks accepts the message & then offloads it to it's own processing thread/task to avoid blocking your monitoring tasks & holding up the other notifications.

Upvotes: 2

Related Questions