How to invoke multiple instances using timer events in C# - c#

Problem Statement
I have a module code written in C# which waits for a topic called 'start' and as soon as it's received, then I need to send certain data periodically based om time interval. I need to accommodate my logic in such a way that 'start' topic can be called multiple times with different metadata value and all those has to run it's own instance with the same logic.
Issue I am Facing
Now the code always runs only one instance of 'SendPeriodicTelemetry' even though 'start' topic is invoked multiple times.
Code Snippet Sample
The below three classes are given with the order the call goes
public static async void MessageReceiveAsync(object sender, MqttApplicationMessageReceivedEventArgs e)
{
Console.WriteLine($"Message received on topic: '{e.ApplicationMessage.Topic}'. Payload: {msg}");
try
{
if (e.ApplicationMessage.Topic.Contains("methods"))
{
await PublisherClass.PublishToMethod(topicElement, msg);
}
}
}
public static class PublisherClass
{
public static bool StartInvoked { get; set; }
public static int StartCmdCount { get; set; } = 0;
public static async Task PublishToMethod(string methodName, string message)
{
try
{
string topic;
var sendPeriodicTelemetries = new SendPeriodicTelemetry[10];
switch (methodName)
{
case "stop":
for (int i = 0; i < StartCmdCount; i++)
{
Console.WriteLine($"start command index to stop: {i}");
sendPeriodicTelemetries[StartCmdCount].aTimer.Stop();
sendPeriodicTelemetries[StartCmdCount].aTimer.Dispose();
}
StartInvoked = false;
StartCmdCount = 0;
await CommonMethods.AckMethod(CommonMethods.listMessage, CommonMethods.requestId, MethodNames.sendTimeSeries);
break;
case "start":
sendPeriodicTelemetries[StartCmdCount] = new SendPeriodicTelemetry();
sendPeriodicTelemetries[StartCmdCount].TsAlarmAndEventTopicAndPayload(rawPayload, objId);
sendPeriodicTelemetries[StartCmdCount].aTimer = new System.Timers.Timer(timeIntervel);
sendPeriodicTelemetries[StartCmdCount].SetTimer(timeIntervel, sendPeriodicTelemetries[StartCmdCount].aTimer);
StartCmdCount++;
break;
}
}
catch(Exception exception)
{
Console.WriteLine($"Exception occurred: {exception.ToString()}");
}
}
}
public class SendPeriodicTelemetry
{
public void TsAlarmAndEventTopicAndPayload(JObject rawPayload, string objId)
{
}
public void SetTimer(int timeInterval, Timer timer)
{
if (!PublisherClass.StartInvoked)
{
PublisherClass.StartInvoked = true;
timer.Elapsed += SendPeriodic;
timer.AutoReset = true;
timer.Enabled = true;
}
}
public async void SendPeriodic(Object source, ElapsedEventArgs e)
{
Console.WriteLine("Starting to send periodic Telemetry at {0:HH:mm:ss.fff}", e.SignalTime);
await SendPeriodicTimeSeries(PublisherClass.TimeSeriesTopic, PublisherClass.TelemetrySingle, PublisherClass.Configuration);
await SendPeriodicAlarm(PublisherClass.AlarmTopic, PublisherClass.AlarmSingle);
await SendPeriodicEvent(PublisherClass.EventTopic, PublisherClass.EventSingle);
}
public async Task SendPeriodicTimeSeries(string topic, TelemetrySingle payload, JObject configuration)
{
}
public async Task SendPeriodicAlarm(string topic, AlarmSingle payload)
{
}
public async Task SendPeriodicEvent(string topic, EventSingle payload)
{
}
}
Scenario Flow
Consider from 'MessageReceiveAsync' I received a topic that contains a method(not C# method) called 'start' with some metadata for the first time then the 'PublishToMethod' is invoked with that method name ('start')
Then based on the metadata received inside 'start' case I initiate the timer to particular interval based on the metadata received along-with 'start' command.
Then all three below data will be sent on the set time intervals
await SendPeriodicTimeSeries(PublisherClass.TimeSeriesTopic,
PublisherClass.TelemetrySingle, PublisherClass.Configuration);
await SendPeriodicAlarm(PublisherClass.AlarmTopic, PublisherClass.AlarmSingle);
await SendPeriodicEvent(PublisherClass.EventTopic, PublisherClass.EventSingle);
Consider from 'MessageReceiveAsync' I received another topic that contains a method(not C# method) called 'start' again for the second time with a different metadata. It can hit any number of times.
Then again the specific timeinterval should be set for the timer and timer event has to go periodically for different kind of metadata
The previous periodic data should also go ahead as usual
Also whenever a 'stop' method is invoked the periodic timer events for all instances should stop.
Actual Result
As soon as I receive the 'start' for the second time the first invoked metadata values all switched to the second time invoked values and always the periodic data goes for the latest invoked value. Only one instance is running always. I want to send periodic data for as many instances as the number of times 'start' is invoked.
Expected Result
Multiple instances of timer event should get invoked and the below methods should keep sending the data according to the metadata received
await SendPeriodicTimeSeries(PublisherClass.TimeSeriesTopic,
PublisherClass.TelemetrySingle, PublisherClass.Configuration);
await SendPeriodicAlarm(PublisherClass.AlarmTopic, PublisherClass.AlarmSingle);
await SendPeriodicEvent(PublisherClass.EventTopic, PublisherClass.EventSingle);

There was a problem in Timer object initialization. It was still from a static class and hence changed that with the 'SendPeriodicTelemetry' instance level property. Then now I could able to invoke multiple instances with the specified time interval. And for stopping those time based events I need to dispose the Timer. That's it. Achieved my objective. And it's not required to initialize the
'SendPeriodicTelemetry' object as array to achieve that.
public static async void MessageReceiveAsync(object sender, MqttApplicationMessageReceivedEventArgs e)
{
Console.WriteLine($"Message received on topic: '{e.ApplicationMessage.Topic}'. Payload: {msg}");
try
{
if (e.ApplicationMessage.Topic.Contains("methods"))
{
await PublisherClass.PublishToMethod(topicElement, msg);
}
}
}
public static class PublisherClass
{
public static List<System.Timers.Timer> SendPeriodicTimerObjs { get; set; } = new List<System.Timers.Timer>();
public static async Task PublishToMethod(string methodName, string message)
{
try
{
switch (methodName)
{
case "start":
case "stop":
var sendPeriodicObj = new SendPeriodicTelemetry();
sendPeriodicObj.periodicTimer = new System.Timers.Timer();
SendPeriodicTimerObjs.Add(sendPeriodicObj.periodicTimer);
if (methodName == "start")
{
sendPeriodicObj.TsAlarmAndEventTopicAndPayload(rawPayload, objId);
sendPeriodicObj.periodicTimer.Interval = timeIntervel;
sendPeriodicObj.SetTimer(timeIntervel, sendPeriodicObj.periodicTimer);
}
else
{
sendPeriodicObj.periodicTimer.Stop();
foreach(var timer in SendPeriodicTimerObjs)
{
timer.Dispose();
}
SendPeriodicTimerObjs.Clear();
}
break;
}
}
catch(Exception exception)
{
Console.WriteLine($"Exception occurred: {exception.ToString()}");
}
}
}
public class SendPeriodicTelemetry
{
public System.Timers.Timer periodicTimer;
public void TsAlarmAndEventTopicAndPayload(JObject rawPayload, string objId)
{
}
public void SetTimer(int timeInterval, Timer timer)
{
Console.WriteLine($"Inside SetTimer method");
StartInvoked = true;
timer.Elapsed += SendPeriodic;
timer.AutoReset = true;
timer.Enabled = true;
}
public async void SendPeriodic(Object source, ElapsedEventArgs e)
{
Console.WriteLine("Starting to send periodic Telemetry at {0:HH:mm:ss.fff}", e.SignalTime);
await SendPeriodicTimeSeries(PublisherClass.TimeSeriesTopic, PublisherClass.TelemetrySingle, PublisherClass.Configuration);
await SendPeriodicAlarm(PublisherClass.AlarmTopic, PublisherClass.AlarmSingle);
await SendPeriodicEvent(PublisherClass.EventTopic, PublisherClass.EventSingle);
}
public async Task SendPeriodicTimeSeries(string topic, TelemetrySingle payload, JObject configuration)
{
}
public async Task SendPeriodicAlarm(string topic, AlarmSingle payload)
{
}
public async Task SendPeriodicEvent(string topic, EventSingle payload)
{
}
}

Related

How can I invoke a function at special position after receiving outer special message in C#?

Let us look at a segment of a code: csharp
public void TestMethod(){
//do something
string a = await WaitingObj.Read();
//continue to do something
}
And we have a message queue, which is receiving messages all the time. When it finds the message is what the TestMethod() needs, it will pass this message to WaitingObj.Read() which will return the value and pass the value to the string a.
However, We know we cannot invoke Read() twice (in the TestMethod() to pass the value and when the queue receives new messages to judge whether the message is the TestMethod() needs).
So, How can I solve this problem with Await/Async or design the programs.
The message queue in the problem is just a simple queue structure in basic data structure.
The function of WaitingObj.Read() is just indicating when the data is ready for passing to string a and string a can directly use it can continue carrying the rest of codes.
After reading through your post and all the comments, I noticed in particular where you said:
I wonder if I can just solve it by designing WaitingObj.Read()....
Let's entertain that thought by designing a Queue that provides some basic observability by implementing INotifyCollectionChanged and provides these features:
A ReadAsync method to await a "special" message that matches a specified predicate.
A SelfTest method that enqueues one message per second from a list of 10 messages.
An instance of var WaitingObj = new DesignedObservableQueue() can then be exercised in a console app to see whether or not this would satisfy your design specs.
Designed Queue (a.k.a. "WaitingObj")
class DesignedObservableQueue : Queue<MockMessage>, INotifyCollectionChanged
{
public new void Enqueue(MockMessage message)
{
base.Enqueue(message);
CollectionChanged?
.Invoke(
this,
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
message));
}
public new MockMessage Dequeue()
{
var message = base.Dequeue();
CollectionChanged?
.Invoke(
this,
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove,
message));
return message;
}
public event NotifyCollectionChangedEventHandler? CollectionChanged;
Provide a way to detect that a special message has been enqueued.
public async Task ReadAsync(Predicate<MockMessage> condition)
{
var awaiter = new SemaphoreSlim(0, 1);
try
{
CollectionChanged += localOnCollectionChanged;
await awaiter.WaitAsync();
}
finally
{
awaiter.Release();
CollectionChanged -= localOnCollectionChanged;
}
void localOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
var message = e.NewItems!.Cast<MockMessage>().First();
if(condition(message))
{
Console.WriteLine($"MATCH: {message.Message}");
awaiter.Release();
}
else
{
Console.WriteLine($"NO MATCH: {message.Message}");
}
break;
default:
break;
}
}
}
Mock a queue that "is receiving messages all the time" by self-enqueuing at one-second intervals.
public async Task SelfTest(CancellationToken token)
{
foreach (
var message in new[]
{
"occasion",
"twin",
"intention",
"arrow",
"draw",
"forest",
"special",
"please",
"shell",
"momentum",
})
{
if(token.IsCancellationRequested) return;
Enqueue(new MockMessage { Message = message });
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
}
Exercise TestMethod
Once the TestMethod shown in your post is changed to an async method, perform this minimal test:
static void Main(string[] args)
{
Console.Title = "Test Runner";
var stopwatch = new Stopwatch();
var WaitingObj = new DesignedObservableQueue();
// Local test method is expecting to match
// the predicate in ~6 seconds so allow 10.
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
stopwatch.Start();
_ = WaitingObj.SelfTest(cts.Token);
try
{
TestMethod().Wait(cts.Token);
Console.WriteLine($"PASSED {stopwatch.Elapsed}");
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"FAILED {stopwatch.Elapsed}");
}
// Local test method
async Task TestMethod()
{
// do something
await WaitingObj.ReadAsync((message) => message.Message == "special");
// continue to do something
}
Console.ReadKey();
}
Where:
class MockMessage
{
public string Message { get; set; } = string.Empty;
}

(A)wait for class to finish instantiate

I try to wait for the class to be finished with instantiate.
My architecture is the following. Cook is inheriade from CookChief.
And if I instantiate cook, CookChief is creating himself, but CookChief is calling 1 other class named Cookhelper the cookhelper is waiting for a input and for this input method i want to wait in Cook.
The thing is iam creating this in MVVM Galasoft and my entry point is the CookViewmodel, with a relaycommand.
In the code below you can see my architecture. To say it short I want to wait until this bool processed = await Task.Run(() => ValidateForDeviceId()); is finished.
My first step was to outsource the constructer of each class. And create a init method.
This is my code:
public CookViewModel()
{
startCookButtonCommand = new RelayCommand(Cook);
}
private async Task Cook()
{
cook.Init();
}
public class Cook : CookChief
{
public Cook()
{
}
public async Task Init()
{
await this.CookChiefInit();
//here I want to wait until CookChiefInit is finished
Cooking();
}
public void Cooking()
{
MessageBox.Show("Input received");
}
}
Now the Cookchief:
public Cookchief()
{
}
protected async Task CookchiefInit()
{
this.Cookhelper = new Cookhelper();
Cookhelper.CookHelperInit();
}
And in the CookHelper we do this:
public CookHelper()
{
}
public void CookHelperInit()
{
this.driverWindow = new DriverWindow();
startProc();
}
private async void startProc()
{
ShowOrCloseDriverWindow(true);
//this is the task what we wait for before we can repeat
bool processed = await Task.Run(() => ValidateForDeviceId());
if(processed)
{
ShowOrCloseDriverWindow(false);
}
else
{
MessageBox.Show("DriverError");
}
}
private bool ValidateForDeviceId()
{
for (; ; )
{
this.deviceId = Input.deviceId;
if (deviceId > 0)
{
break;
}
}
return true;
}
Per the discussion in the comments, the problem here was that the initialization routine mixed synchronous and asynchronous methods and calls. Additionally, some async methods were called without the await keyword. The solution was to make all calls asynchronous and await them.
cook.Init() needs an await:
private async Task Cook()
{
await cook.Init();
}
In CookchiefInit(), the CookHelperInit() call needs to be awaited:
protected async Task CookchiefInit()
{
this.Cookhelper = new Cookhelper();
Cookhelper.CookHelperInit();
}
In order to await CookHelperInit(), it needs to be made asynchronous. The startProc() call is to an async method, so it must also be awaited:
public async Task CookHelperInit()
{
this.driverWindow = new DriverWindow();
await startProc();
}

Management of Task.Run and stop/cancel of method and get progress

I have an application which currently runs Tasks on a time interval, however I would like more control over that, to be able to stop a running task and restart it by clicking a UI.
There are 6 tasks at the moment, but I would want to keep things generic, to be able to easily more when required. I was hoping to be able to create a wrapper to control them, which I can pass a method into as a parameter.
As such I created an object, which I create as many of as there are tasks, I can get status updates from it as well as manage it
I want to:
- Start a method/Task
- Stop a method/Task
- Restart a method/Task
- Get feedback from it's log/updates/progress/errors that I record to updates List
Is this a good way to do this, is there a better way to achieve what I'm after?
public class ManagedTask
{
public ManagedTask()
{
CreateNewToken();
}
public int Id { get; set; }
public string DescriptiveName { get; set; }
public Action<CancellationToken> TheVoidToRun { private get; set; }
private CancellationTokenSource CTokenSource { get; set; }
private CancellationToken CToken { get; set; }
private Task TheRunningThing { get; set; }
public void StartIt()
{
if (TheRunningThing == null || TheTaskStatus() == TaskStatus.Canceled || TheTaskStatus() == TaskStatus.RanToCompletion)
{
CreateNewToken();
}
// Start up the Task
AddUpdate($"Starting Task at {DateTime.Now}");
TheRunningThing = Task.Run(() => TheVoidToRun?.Invoke(CToken), CToken);
AddUpdate($"Started Task at {DateTime.Now}");
}
public void EndIt()
{
AddUpdate($"Cancelling Task at {DateTime.Now}");
CTokenSource.Cancel();
// Do - If in progress try to stop (Cancellation Token)
// Do - Stop future repeats
}
private void CreateNewToken()
{
CTokenSource = new CancellationTokenSource();
CTokenSource.Token.ThrowIfCancellationRequested();
CToken = CTokenSource.Token;
}
public TaskStatus TheTaskStatus() => TheRunningThing.Status;
internal List<string> Updates { get; set; }
private void AddUpdate(string updates)
{
// Do stuff
}
}
So I have various methods which I'd like to pass into this such like:
public class AvailableTasks
{
public async void DoStuffThatIsCancelable(CancellationToken token)
{
DoTheLongStuffOnRepeat(token);
}
public async void DoAnotherThingThatIsCancelable(CancellationToken token)
{
DoTheLongStuffOnRepeat(token);
}
private async void DoTheLongStuffOnRepeat(CancellationToken token)
{
// Do stuff
for (int i = 0; i < 20; i++)
{
while (!token.IsCancellationRequested)
{
try
{
await Task.Delay(500, token);
}
catch (TaskCanceledException ex)
{
Console.WriteLine("Task was cancelled");
continue;
}
Console.WriteLine($"Task Loop at {(i + 1) * 500}");
}
}
}
}
Here is how I was thinking of calling it.
private static readonly List<ManagedTask> _managedTasks = new List<ManagedTask>();
public static void SetupManagedTasks()
{
var at = new AvailableTasks();
var mt1 = new ManagedTask
{
Id = 1,
DescriptiveName = "The cancelable task",
TheVoidToRun = at.DoStuffThatIsCancelable,
};
_managedTasks.Add(mt1);
var mt2 = new ManagedTask
{
Id = 2,
DescriptiveName = "Another cancelable task",
TheVoidToRun = at.DoAnotherThingThatIsCancelable,
};
_managedTasks.Add(mt2);
mt1.StartIt();
mt2.StartIt();
Console.WriteLine($"{mt1.DescriptiveName} status: {mt1.TheTaskStatus()}");
Console.WriteLine($"{mt2.DescriptiveName} status: {mt2.TheTaskStatus()}");
}
public static void CancelTask(int id)
{
var mt = _managedTasks.FirstOrDefault(t => t.Id == id);
if (mt != null)
{
mt.EndIt();
Console.WriteLine($"{mt.DescriptiveName} status: {mt.TheTaskStatus()}");
}
}
public static void GetTaskStatus(int id)
{
var mt = _managedTasks.FirstOrDefault(t => t.Id == id);
if (mt != null)
{
Console.WriteLine($"{mt.DescriptiveName} status: {mt.TheTaskStatus()}");
}
}
However even with all the above, I suffer from the Status only ever showing RanToCompletion.
How can I structure the above to achieve what I want?
Thanks,
David
I suffer from the Status only ever showing RanToCompletion.
This is because your methods are using async void. They should be async Task. As I describe in my async best practices article, you should avoid async void.
Other notes...
Start a method/Task
Restart a method/Task
You can start (or restart) a task on the thread pool by using Task.Run. However, if you have naturally asynchronous tasks, then you can represent them as Func<Task> and just invoke the Func<Task> to start them.
Stop a method/Task
The only appropriate way to do this is with a CancellationToken, which it looks like you're using correctly.
Get feedback from it's log/updates/progress/errors that I record to updates List
I recommend using IProgress<T> for any kind of progress updates.

Control flow in asynchronous calls

I'm facing difficulties understanding how to handle program control during asynchronous flow.
I have a SessionManager class which calls the initiates the session and we need to register
for the event OnStartApplicationSessionResponse and my control will return to the calling point. I will get the session id in the eventhandler after sometime or the error code if there is an error.
class SessionManager
{
public bool startUp(Object params)
{
try
{
serviceProvider = new ServiceProvider();
serviceProvider.OnStartApplicationSessionResponse += new StartApplicationSessionResponseHandler(ServiceProvider_OnStartApplicationSessionResponse);
serviceProvider.startUp(params);
}
}
public void ServiceProvider_OnStartApplicationSessionResponse(object sender, ServiceProvider.StartApplicationSessionResponseArgs e)
{
//e.getError
//I will get the session Id here or error code
}
}
How do I get sessionId or the error as my control is now at the calling position?
You could use TaskCompletionSource to make the Event awaitable.
class SessionManager
{
private ServiceProvider _serviceProvider;
public int SessionId
{
get;
private set;
}
public Task<bool> StartUp(Object param)
{
_serviceProvider = new ServiceProvider();
var tcs = new TaskCompletionSource<bool>();
_serviceProvider.OnStartApplicationSessionResponse += (sender, args) =>
{
// do your stuff
// e.g.
SessionId = 0xB00B5;
tcs.SetResult(true);
};
_serviceProvider.startUp(param);
return tcs.Task;
}
}
The call would look like:
private static async void SomeButtonClick()
{
var mgr = new SessionManager();
var success = await mgr.StartUp("string");
if (success)
{
Console.WriteLine(mgr.SessionId);
// update ui or whatever
}
}
note: This Feature is available in .Net 4.5.
With the C# feature async and await you are able to rewrite an asynchronous flow into something that is like a synchronous flow. You have only provided some fragments of your code so to provide a complete example I have created some code that resembles your code:
class StartEventArgs : EventArgs {
public StartEventArgs(Int32 sessionId, Int32 errorCode) {
SessionId = sessionId;
ErrorCode = errorCode;
}
public Int32 SessionId { get; private set; }
public Int32 ErrorCode { get; private set; }
}
delegate void StartEventHandler(Object sender, StartEventArgs e);
class ServiceProvider {
public event StartEventHandler Start;
public void Startup(Boolean succeed) {
Thread.Sleep(TimeSpan.FromSeconds(1));
if (succeed)
OnStart(new StartEventArgs(321, 0));
else
OnStart(new StartEventArgs(0, 123));
}
protected void OnStart(StartEventArgs e) {
var handler = Start;
if (handler != null)
handler(this, e);
}
}
The ServiceProvider.Startup method will delay for a second before firing an event that either signals success or failure depending on the succeed parameter provided. The method is rather silly but hopefully is similar to the behavior of your ServiceProvider.Startup method.
You can convert the asynchronous startup into a task using a TaskCompletionSource:
Task<Int32> PerformStartup(ServiceProvider serviceProvider, Boolean succeed) {
var taskCompletionSource = new TaskCompletionSource<Int32>();
serviceProvider.Start += (sender, e) => {
if (e.ErrorCode > 0)
throw new Exception(e.ErrorCode.ToString());
taskCompletionSource.SetResult(e.SessionId);
};
serviceProvider.Startup(succeed);
return taskCompletionSource.Task;
}
Notice how an error signaled by the Start event is converted into an Exception (in production code you should use a custom exception type instead).
Using the async and await feature of C# you can now write code that looks very much like synchronous code even though it actually is asynchronous:
async void Startup(Boolean succeed) {
var serviceProvider = new ServiceProvider();
try {
var sessionId = await PerformStartup(serviceProvider, succeed);
Console.WriteLine(sessionId);
}
catch (Exception ex) {
Console.WriteLine(ex);
}
}
If an error is reported by the Start event you can now deal with in the catch block. Also the session ID is simply a return value of the function. The "magic" is that using await on a Task will return the result of the task when it completes and if an exception is thrown in the task it can be caught on the thread awaiting the task.

TPL Queue Processing

I'm currently working on a a project and I have a need to queue some jobs for processing, here's the requirement:
Jobs must be processed one at a time
A queued item must be able to be waited on
So I want something akin to:
Task<result> QueueJob(params here)
{
/// Queue the job and somehow return a waitable task that will wait until the queued job has been executed and return the result.
}
I've tried having a background running task that just pulls items off a queue and processes the job, but the difficulty is getting from a background task to the method.
If need be I could go the route of just requesting a completion callback in the QueueJob method, but it'd be great if I could get a transparent Task back that allows you to wait on the job to be processed (even if there are jobs before it in the queue).
You might find TaskCompletionSource<T> useful, it can be used to create a Task that completes exactly when you want it to. If you combine it with BlockingCollection<T>, you will get your queue:
class JobProcessor<TInput, TOutput> : IDisposable
{
private readonly Func<TInput, TOutput> m_transform;
// or a custom type instead of Tuple
private readonly
BlockingCollection<Tuple<TInput, TaskCompletionSource<TOutput>>>
m_queue =
new BlockingCollection<Tuple<TInput, TaskCompletionSource<TOutput>>>();
public JobProcessor(Func<TInput, TOutput> transform)
{
m_transform = transform;
Task.Factory.StartNew(ProcessQueue, TaskCreationOptions.LongRunning);
}
private void ProcessQueue()
{
Tuple<TInput, TaskCompletionSource<TOutput>> tuple;
while (m_queue.TryTake(out tuple, Timeout.Infinite))
{
var input = tuple.Item1;
var tcs = tuple.Item2;
try
{
tcs.SetResult(m_transform(input));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}
}
public Task<TOutput> QueueJob(TInput input)
{
var tcs = new TaskCompletionSource<TOutput>();
m_queue.Add(Tuple.Create(input, tcs));
return tcs.Task;
}
public void Dispose()
{
m_queue.CompleteAdding();
}
}
I would go for something like this:
class TaskProcessor<TResult>
{
// TODO: Error handling!
readonly BlockingCollection<Task<TResult>> blockingCollection = new BlockingCollection<Task<TResult>>(new ConcurrentQueue<Task<TResult>>());
public Task<TResult> AddTask(Func<TResult> work)
{
var task = new Task<TResult>(work);
blockingCollection.Add(task);
return task; // give the task back to the caller so they can wait on it
}
public void CompleteAddingTasks()
{
blockingCollection.CompleteAdding();
}
public TaskProcessor()
{
ProcessQueue();
}
void ProcessQueue()
{
Task<TResult> task;
while (blockingCollection.TryTake(out task))
{
task.Start();
task.Wait(); // ensure this task finishes before we start a new one...
}
}
}
Depending on the type of app that is using it, you could switch out the BlockingCollection/ConcurrentQueue for something simpler (eg just a plain queue). You can also adjust the signature of the "AddTask" method depending on what sort of methods/parameters you will be queueing up...
Func<T> takes no parameters and returns a value of type T. The jobs are run one by one and you can wait on the returned task to get the result.
public class TaskQueue
{
private Queue<Task> InnerTaskQueue;
private bool IsJobRunning;
public void Start()
{
Task.Factory.StartNew(() =>
{
while (true)
{
if (InnerTaskQueue.Count > 0 && !IsJobRunning)
{
var task = InnerTaskQueue.Dequeue()
task.Start();
IsJobRunning = true;
task.ContinueWith(t => IsJobRunning = false);
}
else
{
Thread.Sleep(1000);
}
}
}
}
public Task<T> QueueJob(Func<T> job)
{
var task = new Task<T>(() => job());
InnerTaskQueue.Enqueue(task);
return task;
}
}

Categories