Remove cancelled Task from producer/consumer queue - c#

I want to use an async producer/consumer queue (AsyncEx lib) to send messages one at a time over a bus. Right now I achieve this simply by async blocking. It's working fine, but I have no control over the queue :(
So I came up with following solution, problem is that a canceled task is not removed from the queue. If I limit the queue to say 10 (because each message takes 1s to send and max queue time shall be 10s or so) and the queue contains already 8 waiting tasks and 2 canceled tasks, than the next queued task would throw an InvalidOperationException although the two canceled task wouldn't be sent anyway.
Maybe there is a better way to do this :D
class Program
{
static AsyncProducerConsumerQueue<Tuple<string, TaskCompletionSource>> s_Queue =
new AsyncProducerConsumerQueue<Tuple<string, TaskCompletionSource>>();
static void Main()
{
StartAsync().Wait();
}
static async Task StartAsync()
{
var sendingTask = StartSendingAsync();
var tasks = new List<Task>();
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(8)))
{
for (var i = 0; i < 10; i++)
{
tasks.Add(EnqueueMessageAsync("Message " + i, cts.Token));
}
try
{
await Task.WhenAll(tasks);
Console.WriteLine("All messages sent.");
}
catch (TaskCanceledException)
{
Console.WriteLine("At least one task was canceled.");
}
}
s_Queue.CompleteAdding();
await sendingTask;
s_Queue.Dispose();
Console.WriteLine("Queue completed.");
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
static async Task EnqueueMessageAsync(string message, CancellationToken token)
{
var tcs = new TaskCompletionSource();
using (token.Register(() => tcs.TrySetCanceled()))
{
await s_Queue.EnqueueAsync(new Tuple<string, TaskCompletionSource>(message, tcs));
Console.WriteLine("Thread '{0}' - {1}: {2} queued.", Thread.CurrentThread.ManagedThreadId, DateTime.Now.TimeOfDay, message);
await tcs.Task;
}
}
static async Task SendMessageAsync(string message)
{
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine("Thread '{0}' - {1}: {2} sent.", Thread.CurrentThread.ManagedThreadId, DateTime.Now.TimeOfDay, message);
}
static async Task StartSendingAsync()
{
while (await s_Queue.OutputAvailableAsync())
{
var t = await s_Queue.DequeueAsync();
if (t.Item2.Task.IsCanceled || t.Item2.Task.IsFaulted) continue;
await SendMessageAsync(t.Item1);
t.Item2.TrySetResult();
}
}
}
Edit 1:
As svik pointed out the InvalidOperationException is only thrown if the queue is already completed. So this solution doesn't even solve my initial problem of an unmanaged "queue" of waiting tasks. If there are e.g. more than 10 calls/10s I got a full queue and an additional unmanaged "queue" of waiting tasks like with my async blocking approach (AsyncMonitor). I guess I have to come up with some other solution then...
Edit 2:
I have N different producers of messages (I don't know how many there are because it's not my code) and only one consumer that sends the messages over a bus and checks if they were sent correctly (not really string messages).
The following code simulates a situation where the code should break (queue size is 10):
Enqueue 10 messages (with an timeout of 5sec)
Wait 5sec (message 0-4 were sent and message 5-9 were cancelled)
Enqueue 11 new messages (w/o timeout)
Message 10 - 19 should be enqueued because the queue only contains cancelled messages
Message 20 should throw an exception (e.g. QueueOverflowException) because the queue is full, this would be handled or not by the producer code
Producers:
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
for (var i = 0; i < 10; i++) { tasks.Add(EnqueueMessageAsync("Message " + i, cts.Token)); }
await Task.Delay(TimeSpan.FromSeconds(5));
for (var i = 10; i < 21; i++) { tasks.Add(EnqueueMessageAsync("Message " + i, default(CancellationToken))); }
try
{
await Task.WhenAll(tasks);
Console.WriteLine("All messages sent.");
}
catch (TaskCanceledException)
{
Console.WriteLine("At least one task was canceled.");
Console.WriteLine("Press any key to complete queue...");
Console.ReadKey();
}
}
The goal is, I want to have full control over all messages that should be send, but this is not the case in the code I've posted before, because I only have control over the messages in the queue but not the messages that are waiting to be enqueued (there could be 10000 messages asynchronously waiting to be enqueued and I wouldn't know => producer code wouldn't work as expected anyway because it would take forever to send all the messages that are waiting...)
I hope this makes it clearer what I want to achieve ;)

I'm not sure if answering my own questing is OK, so I won't flag it as answer, maybe someone comes up with a better solution :P
First of all here is the producer code:
static async Task StartAsync()
{
using (var queue = new SendMessageQueue(10, new SendMessageService()))
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(4.5)))
{
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
{
tasks.Add(queue.SendAsync(i.ToString(), timeoutTokenSource.Token));
}
await Task.Delay(TimeSpan.FromSeconds(4.5));
for (var i = 10; i < 25; i++)
{
tasks.Add(queue.SendAsync(i.ToString(), default(CancellationToken)));
}
await queue.CompleteSendingAsync();
for (var i = 0; i < tasks.Count; i++ )
{
try
{
await tasks[i];
Console.WriteLine("Message '{0}' send.", i);
}
catch (TaskCanceledException)
{
Console.WriteLine("Message '{0}' canceled.", i);
}
catch (QueueOverflowException ex)
{
Console.WriteLine(ex.Message);
}
}
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
25 messages are enqueued over 5sec
16 messages are sent
3 messages are not sent (queue is full)
6 messages get canceled
And here is the "Queue" class that is based upon a List. It's a combination of the queue and the consumer. Synchronisation is done with the AsyncMonitor class (AsyncEx by Stephen Cleary).
class SendMessageQueue : IDisposable
{
private bool m_Disposed;
private bool m_CompleteSending;
private Task m_SendingTask;
private AsyncMonitor m_Monitor;
private List<MessageTaskCompletionSource> m_MessageCollection;
private ISendMessageService m_SendMessageService;
public int Capacity { get; private set; }
public SendMessageQueue(int capacity, ISendMessageService service)
{
Capacity = capacity;
m_Monitor = new AsyncMonitor();
m_MessageCollection = new List<MessageTaskCompletionSource>();
m_SendMessageService = service;
m_SendingTask = StartSendingAsync();
}
public async Task<bool> SendAsync(string message, CancellationToken token)
{
if (m_Disposed) { throw new ObjectDisposedException(GetType().Name); }
if (message == null) { throw new ArgumentNullException("message"); }
using (var messageTcs = new MessageTaskCompletionSource(message, token))
{
await AddAsync(messageTcs);
return await messageTcs.Task;
}
}
public async Task CompleteSendingAsync()
{
if (m_Disposed) { throw new ObjectDisposedException(GetType().Name); }
using (m_Monitor.Enter())
{
m_CompleteSending = true;
}
await m_SendingTask;
}
private async Task AddAsync(MessageTaskCompletionSource message)
{
using (await m_Monitor.EnterAsync(message.Token))
{
if (m_CompleteSending) { throw new InvalidOperationException("Queue already completed."); }
if (Capacity < m_MessageCollection.Count)
{
m_MessageCollection.RemoveAll(item => item.IsCanceled);
if (Capacity < m_MessageCollection.Count)
{
throw new QueueOverflowException(string.Format("Queue overflow; '{0}' couldn't be enqueued.", message.Message));
}
}
m_MessageCollection.Add(message);
}
m_Monitor.Pulse(); // signal new message
Console.WriteLine("Thread '{0}' - {1}: '{2}' enqueued.", Thread.CurrentThread.ManagedThreadId, DateTime.Now.TimeOfDay, message.Message);
}
private async Task<MessageTaskCompletionSource> TakeAsync()
{
using (await m_Monitor.EnterAsync())
{
var message = m_MessageCollection.ElementAt(0);
m_MessageCollection.RemoveAt(0);
return message;
}
}
private async Task<bool> OutputAvailableAsync()
{
using (await m_Monitor.EnterAsync())
{
if (m_MessageCollection.Count > 0) { return true; }
else if (m_CompleteSending) { return false; }
await m_Monitor.WaitAsync();
return true;
}
}
private async Task StartSendingAsync()
{
while (await OutputAvailableAsync())
{
var message = await TakeAsync();
if (message.IsCanceled) continue;
try
{
var result = await m_SendMessageService.SendMessageAsync(message.Message, message.Token);
message.TrySetResult(result);
}
catch (TaskCanceledException) { message.TrySetCanceled(); }
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (m_Disposed) return;
if (disposing)
{
if (m_MessageCollection != null)
{
var tmp = m_MessageCollection;
m_MessageCollection = null;
tmp.ForEach(item => item.Dispose());
tmp.Clear();
}
}
m_Disposed = true;
}
#region MessageTaskCompletionSource Class
class MessageTaskCompletionSource : TaskCompletionSource<bool>, IDisposable
{
private bool m_Disposed;
private IDisposable m_CancellationTokenRegistration;
public string Message { get; private set; }
public CancellationToken Token { get; private set; }
public bool IsCompleted { get { return Task.IsCompleted; } }
public bool IsCanceled { get { return Task.IsCanceled; } }
public bool IsFaulted { get { return Task.IsFaulted; } }
public MessageTaskCompletionSource(string message, CancellationToken token)
{
m_CancellationTokenRegistration = token.Register(() => TrySetCanceled());
Message = message;
Token = token;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (m_Disposed) return;
if (disposing)
{
TrySetException(new ObjectDisposedException(GetType().Name));
if (m_CancellationTokenRegistration != null)
{
var tmp = m_CancellationTokenRegistration;
m_CancellationTokenRegistration = null;
tmp.Dispose();
}
}
m_Disposed = true;
}
}
#endregion
}
For now I'am OK with this solution; it's gets the job done :D

Related

Restricting SendAsync calls to 5 messages per second

I'm implementing Binance's API.
The documentation says:
WebSocket connections have a limit of 5 incoming messages per second. A message is considered:
A PING frame
A PONG frame
A JSON controlled message (e.g. subscribe, unsubscribe)
For ex. there is a simple web socket wrapper such as the one from the official Binance Connector. According to the limitation above, SendAsync should be restricted 5 messages per second. If a few threads call SendAsync 5 times at the same time (including PING frame which is built-in the ClientWebSocket class), it's going to fail. How can I solve the issue with that limitation gracefully? Using bounded channels is a solution?
public class BinanceWebSocket : IDisposable
{
private IBinanceWebSocketHandler handler;
private List<Func<string, Task>> onMessageReceivedFunctions;
private List<CancellationTokenRegistration> onMessageReceivedCancellationTokenRegistrations;
private CancellationTokenSource loopCancellationTokenSource;
private Uri url;
private int receiveBufferSize;
public BinanceWebSocket(IBinanceWebSocketHandler handler, string url, int receiveBufferSize = 8192)
{
this.handler = handler;
this.url = new Uri(url);
this.receiveBufferSize = receiveBufferSize;
this.onMessageReceivedFunctions = new List<Func<string, Task>>();
this.onMessageReceivedCancellationTokenRegistrations = new List<CancellationTokenRegistration>();
}
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (this.handler.State != WebSocketState.Open)
{
this.loopCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
await this.handler.ConnectAsync(this.url, cancellationToken);
await Task.Factory.StartNew(() => this.ReceiveLoop(loopCancellationTokenSource.Token, this.receiveBufferSize), loopCancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
public async Task DisconnectAsync(CancellationToken cancellationToken)
{
if (this.loopCancellationTokenSource != null)
{
this.loopCancellationTokenSource.Cancel();
}
if (this.handler.State == WebSocketState.Open)
{
await this.handler.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, cancellationToken);
await this.handler.CloseAsync(WebSocketCloseStatus.NormalClosure, null, cancellationToken);
}
}
public void OnMessageReceived(Func<string, Task> onMessageReceived, CancellationToken cancellationToken)
{
this.onMessageReceivedFunctions.Add(onMessageReceived);
if (cancellationToken != CancellationToken.None)
{
var reg = cancellationToken.Register(() =>
this.onMessageReceivedFunctions.Remove(onMessageReceived));
this.onMessageReceivedCancellationTokenRegistrations.Add(reg);
}
}
public async Task SendAsync(string message, CancellationToken cancellationToken)
{
byte[] byteArray = Encoding.ASCII.GetBytes(message);
await this.handler.SendAsync(new ArraySegment<byte>(byteArray), WebSocketMessageType.Text, true, cancellationToken);
}
public void Dispose()
{
this.DisconnectAsync(CancellationToken.None).Wait();
this.handler.Dispose();
this.onMessageReceivedCancellationTokenRegistrations.ForEach(ct => ct.Dispose());
this.loopCancellationTokenSource.Dispose();
}
private async Task ReceiveLoop(CancellationToken cancellationToken, int receiveBufferSize = 8192)
{
WebSocketReceiveResult receiveResult = null;
try
{
while (!cancellationToken.IsCancellationRequested)
{
var buffer = new ArraySegment<byte>(new byte[receiveBufferSize]);
receiveResult = await this.handler.ReceiveAsync(buffer, cancellationToken);
if (receiveResult.MessageType == WebSocketMessageType.Close)
{
break;
}
string content = Encoding.UTF8.GetString(buffer.ToArray());
this.onMessageReceivedFunctions.ForEach(omrf => omrf(content));
}
}
catch (TaskCanceledException)
{
await this.DisconnectAsync(CancellationToken.None);
}
}
}
Second way which I'm not 100% sure it solves it
SendAsync is being called in a loop using Channels. SingleReader is set to true, which means there will be only one consumer at a time. It technically should solve the issue, but I'm not 100% sure because the channel might only be limiting the amount in the buffer.
private readonly Channel<string> _messagesTextToSendQueue = Channel.CreateUnbounded<string>(new UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = false
});
public ValueTask SendAsync(string message)
{
Validations.Validations.ValidateInput(message, nameof(message));
return _messagesTextToSendQueue.Writer.WriteAsync(message);
}
public void Send(string message)
{
Validations.Validations.ValidateInput(message, nameof(message));
_messagesTextToSendQueue.Writer.TryWrite(message);
}
private async Task SendTextFromQueue()
{
try
{
while (await _messagesTextToSendQueue.Reader.WaitToReadAsync())
{
while (_messagesTextToSendQueue.Reader.TryRead(out var message))
{
try
{
await SendInternalSynchronized(message).ConfigureAwait(false);
}
catch (Exception e)
{
Logger.Error(e, L($"Failed to send text message: '{message}'. Error: {e.Message}"));
}
}
}
}
catch (TaskCanceledException)
{
// task was canceled, ignore
}
catch (OperationCanceledException)
{
// operation was canceled, ignore
}
catch (Exception e)
{
if (_cancellationTotal.IsCancellationRequested || _disposing)
{
// disposing/canceling, do nothing and exit
return;
}
Logger.Trace(L($"Sending text thread failed, error: {e.Message}. Creating a new sending thread."));
StartBackgroundThreadForSendingText();
}
}
I would try to keep it as simple as possible and use Semaphore Slim to achieve this, I have created a class to perform this task.
public class ThrottlingLimiter
{
private readonly SemaphoreSlim _semaphore;
private readonly TimeSpan _timeUnit;
public ThrottlingLimiter(int maxActionsPerTimeUnit, TimeSpan timeUnit)
{
if (maxActionsPerTimeUnit < 1)
throw new ArgumentOutOfRangeException(nameof(maxActionsPerTimeUnit));
if (timeUnit < TimeSpan.Zero || timeUnit.TotalMilliseconds > int.MaxValue)
throw new ArgumentOutOfRangeException(nameof(timeUnit));
_semaphore = new SemaphoreSlim(maxActionsPerTimeUnit, maxActionsPerTimeUnit);
_timeUnit = timeUnit;
}
public async Task WaitAsync(CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
ScheduleSemaphoreRelease();
}
private async void ScheduleSemaphoreRelease()
{
await Task.Delay(_timeUnit).ConfigureAwait(false);
_semaphore.Release();
}
}
Now to Use this class, all you have to do is set your limit and timeSpan
public async Task SendData(List<string> allMessages)
{
// Limiting 5 calls per second
ThrottlingLimiter throttlingLimiter = new ThrottlingLimiter(5, TimeSpan.FromSeconds(1));
await Task.WhenAll(allMessages.Select(async message =>
{
await throttlingLimiter.WaitAsync();
try {
await SendInternalSynchronized(message);
// I am not sure what this SendInternalSynchronized returns but I would return some thing to keep a track if this call is successful or not
}
catch (Exception e)
{
Logger.Error(e, L($"Failed to send text message: {message}'. Error: {e.Message}"));
}
});
}
so basically what will happen here is, no matter how big your list is, the ThrottlingLimiter will only send 5 messages per second and wait for the next second to send the next 5 messages.
so, in your case, get all the data from your call to
await _messagesTextToSendQueue.Reader.WaitToReadAsync();
store that into a list or any collection and pass that to the SendData function.

How to prepare a console application that pushes and pops into a redis queue?

I need to prepare a console application with 3 buttons, one that adds elements to a Redis queue, one that pops elements out of it and one that displays elements in the queue. I am new to C# and Redis. Can anyone help me with this, or provide me some resources.
I have coded up the connection to the Redis DB and am able to set and get variables.
using System;
using StackExchange.Redis;
namespace RedisConsoleApp1
{
class Program
{
static void Main(string[] args)
{
ConnectionMultiplexer redisCon = ConnectionMultiplexer.Connect("localhost");
IDatabase db = redisCon.GetDatabase();
//db.Lis
db.StringSet("foo", "dog");
string val = db.StringGet("foo");
Console.WriteLine("output is {0}", val);
Console.ReadKey();
}
}
}
Write a channel subscriber like that
public class RedisHostingRunner : HostedService
{
private readonly IServiceProvider _serviceProvider;
IRedisSubscriber _subscriber;
public RedisHostingRunner(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_subscriber = _serviceProvider.GetRequiredService<RedisSubscriber>();
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
//while (!cancellationToken.IsCancellationRequested)
//{
_subscriber.SubScribeChannel();
//await Task.Delay(TimeSpan.FromSeconds(60), cancellationToken);
//}
}
public Task ShutdownAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
}
And in your subscriber set a message handler
public void SubScribeChannel()
{
_logger.LogInformation("!SubScribeChannel started!!");
string channelName = _config.ActiveChannelName;
var pubSub = _connectionMultiplexer.GetSubscriber();
try
{
pubSub.Subscribe(channelName, async (channel, message) => await MessageActionAsync(message, channel));
}
catch(Exception ex)
{
_logger.LogInformation(String.Format("!error: {0}", ex.Message));
}
Debug.WriteLine("EOF");
}
In your handler do your job
private async Task MessageActionAsync(RedisValue message, string channel)
{
try
{
Transformer t = new Transformer(_logger);
_logger.LogInformation(String.Format("!SubScribeChannel message received on message!! channel: {0}, message: {1}", channel, message));
string transformedMessage = Transformer.TransformJsonStringData2Message(message);
List<Document> documents = Transformer.Deserialize<List<Document>>(transformedMessage);
await MergeToMongoDb(documents, channel);
_logger.LogInformation("!Merged");
}
catch (Exception ex)
{
_logger.LogInformation(String.Format("!error: {0}", ex.Message));
}
}

C# async Deserializion method does not cancel

I'm running into the issue where the task being executed below runs asynchronously but is unable to be cancelled when the serializer reads the memory stream. When the user makes the cancellation request (by pressing a cancel button), a cancellation (the method cancel() is called fromt he token) is made but the task continues.
Service Class:
Async method called from LoadHelper() in Main class
public async void StartTask(Action callback, CancellationToken token)
{
await Task.Run(callback, token);
}
Main Class:
private void LoadHelper()
{
_services.GetInstance<IThreadService>().StartTask(
() => LoadHelperAsync(), _cancelService.Token);
}
Method being run async
private void LoadHelperAsync()
{
var serializer = new DataContractSerializer(typeof(UserDatabase));
string selectedDir;
ComparisonResult = null;
_services.GetInstance<IOpenFileService>().DisplayOpenFileDialog(
"Select a saved comparison",
"Comparison File(*.xml; *.cmp)|*.xml;*.cmp",
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
out selectedDir);
if (!string.IsNullOrEmpty(selectedDir))
{
_dispatchService.BeginInvoke(() => IsExecuting = true);
using (FileStream fileStream = new FileStream(selectedDir, FileMode.Open, FileAccess.Read))
using (DeflateStream compressedStream = new DeflateStream(fileStream, CompressionMode.Decompress))
using (BufferedStream regularStream = new BufferedStream(fileStream))
{
Stream memoryStream;
//Use filename to determine compression
if (selectedDir.EndsWith(".cmp", true, null))
{
memoryStream = compressedStream;
}
else
{
memoryStream = regularStream;
}
Report("Loading comparison");
Report(0);
IsExecuting = true;
ComparisonResult = (UserDatabase)serializer.ReadObject(memoryStream);
memoryStream.Close();
fileStream.Close();
IsExecuting = false;
Report("Comparison loaded");
}
_dispatchService.BeginInvoke(() => IsExecuting = false);
_dispatchService.BeginInvoke(() => ViewResults.ExecuteIfAble());
}
else
{
Report("No comparison loaded");
}
Cancellation code:
This command is binded to a "cancel" button in the view.
CancelCompare = new Command(o => _cancelService.Cancel(), o => IsExecuting);
From the CancellationService class
public class CancellationService : ICancellationService, IDisposable
{
private CancellationTokenSource _tokenSource;
public CancellationService()
{
Reset();
}
public CancellationToken Token { get; private set; }
public void Cancel()
{
_tokenSource.Cancel();
}
public void Reset()
{
_tokenSource = new CancellationTokenSource();
Token = _tokenSource.Token;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource.Cancel();
_tokenSource.Dispose();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Calling _tokenSource.Cancel(); won't do anything to a running task. A cancellation token interrupts task execution only if it was canceled before Task.Run
Take a look:
static void Main(string[] args)
{
using (var tokenSource = new CancellationTokenSource())
{
var aTask = StartTask(() =>
{
while (true)
{
Console.WriteLine("Nothing is going to happen.");
// Some long operation
Thread.Sleep(1000);
}
}, tokenSource.Token);
tokenSource.Cancel();
aTask.Wait();
}
Console.ReadKey();
}
static async Task StartTask(Action callback, CancellationToken cancellationToken)
{
await Task.Run(callback, cancellationToken);
}
If you want to cancel the execution, you should check the cancellation token by yourself, there is no any magic which cancels the task for you:
static void Main(string[] args)
{
using (var tokenSource = new CancellationTokenSource())
{
var aTask = StartTask(cancellationToken =>
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("Will stop before that if canceled.");
// Some long operation
// Also pass the token all way down
Task.Delay(1000, cancellationToken).Wait();
}
}, tokenSource.Token);
// Try 0, 500, 1500 to see the difference
Thread.Sleep(1500);
tokenSource.Cancel();
try
{
aTask.Wait();
}
catch (Exception ex)
{
// AggregateException with OperationCanceledException
Console.WriteLine("Task was canceled.");
Console.WriteLine(ex.ToString());
}
}
Console.ReadKey();
}
static async Task StartTask(Action<CancellationToken> callback, CancellationToken cancellationToken)
{
await Task.Run(() => callback(cancellationToken), cancellationToken);
}
Note that this code only illustrates how the task works, never write anything like this in production.

Can I convert the following to TPL code?

I have the following loop that notifies a list of observers of a certain event:
foreach (var observer in registeredObservers)
{
if (observer != null)
{
observer.OnMessageRecieveEvent(new ObserverEvent(item));
}
}
Is there A way I can use the TPL to possibly notify all the registered Observers at once?
Here is the code that is Executed in the OnMessageRecieveEvent()
public void OnMessageRecieveEvent(ObserverEvent e)
{
SendSignal(e.message.payload);
}
private void SendSignal(Byte[] signal)
{
if (state.WorkSocket.Connected)
{
try
{
// Sends async
state.WorkSocket.BeginSend(signal, 0, signal.Length, 0, new AsyncCallback(SendCallback), state.WorkSocket);
}
catch (Exception e)
{
log.Error("Transmission Failier for ip: " + state.WorkSocket.AddressFamily , e);
}
}
else
{
CloseConnection();
}
}
So my questions is:
How can I do this:
Do I actually want to do this? Would it be beneficial to performance?
Since all a single iteration of your loop does is to start an asynchronous socket operation (which is very fast by itself), you most likely wouldn't benefit from parallelizing your code.
Your foreach loop written as Parallel.ForEach. Approximately.
Parallel.ForEach(registeredObservers, (obs, item) =>
{
if (obs != null)
obs.OnMessageReceivedEvent(new ObserverEvent(item));
});
You can try using a TaskCompletionSource or the Task.FromAsync method to convert your SendSignal method to a Task returning one. Then you can just create a list of tasks and await the result after kicking off all the notifications.
The code might look something like this (untested and uncompiled):
public async Task NotifyObservers()
{
List<Task> notifyTasks = new List<Task>();
foreach (var observer in registeredObservers)
{
if (observer != null)
{
notifyTasks.Add(observer.OnMessageRecieveEvent(new ObserverEvent(item)));
}
}
// asynchronously wait for all the tasks to complete
await Task.WhenAll(notifyTasks);
}
public async Task OnMessageRecieveEvent(ObserverEvent e)
{
await SendSignal(e.message.payload);
}
private Task SendSignal(Byte[] signal)
{
if (!state.WorkSocket.Connected)
{
CloseConnection();
return Task.FromResult<object>(null);
}
else
{
var tcs = new TaskCompletionSource<object>();
try
{
// Sends async
state.WorkSocket.BeginSend(signal, 0, signal.Length, 0, (ar) =>
{
try
{
var socket = (Scoket)ar.AsyncState;
tcs.SetResult(socket.EndSend(ar));
}
catch(Exception ex)
{
tcs.SetException(ex);
}
}
, state.WorkSocket);
}
catch (Exception e)
{
log.Error("Transmission Failier for ip: " + state.WorkSocket.AddressFamily , e);
tcs.SetException(e);
}
}
return tcs.Task;
}

Implementing a timeout in c#

I am new to c#; I have mainly done Java.
I want to implement a timeout something along the lines:
int now= Time.now();
while(true)
{
tryMethod();
if(now > now+5000) throw new TimeoutException();
}
How can I implement this in C#? Thanks!
One possible way would be:
Stopwatch sw = new Stopwatch();
sw.Start();
while(true)
{
tryMethod();
if(sw.ElapsedMilliseconds > 5000) throw new TimeoutException();
}
However you currently have no way to break out of your loop. I would recommend having tryMethod return a bool and change it to:
Stopwatch sw = new Stopwatch();
sw.Start();
while(!tryMethod())
{
if(sw.ElapsedMilliseconds > 5000) throw new TimeoutException();
}
The question is quite old, but yet another option.
using(CancellationTokenSource cts = new CancellationTokenSource(5000))
{
cts.Token.Register(() => { throw new TimeoutException(); });
while(!cts.IsCancellationRequested)
{
tryMethod();
}
}
Technically, you should also propagate the CancellationToken in the tryMethod() to interupt it gracefully.
Working demo: (note I had to remove the exception throwing behavior as .netfiddle doesn't like it.)
https://dotnetfiddle.net/WjRxyk
I think you could do this with a timer and a delegate, my example code is below:
using System;
using System.Timers;
class Program
{
public delegate void tm();
static void Main(string[] args)
{
var t = new tm(tryMethod);
var timer = new Timer();
timer.Interval = 5000;
timer.Start();
timer.Elapsed += (sender, e) => timer_Elapsed(t);
t.BeginInvoke(null, null);
}
static void timer_Elapsed(tm p)
{
p.EndInvoke(null);
throw new TimeoutException();
}
static void tryMethod()
{
Console.WriteLine("FooBar");
}
}
You have tryMethod, you then create a delegate and point this delegate at tryMethod, then you start this delegate Asynchronously. Then you have a timer, with the Interval being 5000ms, you pass your delegate into your timer elapsed method (which should work as a delegate is a reference type, not an value type) and once the 5000 seconds has elapsed, you call the EndInvoke method on your delegate.
As long as tryMethod() doesn't block this should do what you want:
Not safe for daylight savings time or changing time zones when mobile:
DateTime startTime = DateTime.Now;
while(true)
{
tryMethod();
if(DateTime.Now.Subtract(startTime).TotalMilliseconds > 5000)
throw new TimeoutException();
}
Timezone and daylight savings time safe versions:
DateTime startTime = DateTime.UtcNow;
while(true)
{
tryMethod();
if(DateTime.UtcNow.Subtract(startTime).TotalMilliseconds > 5000)
throw new TimeoutException();
}
(.NET 3.5 or higher required for DateTimeOffset.)
DateTimeOffset startTime = DateTimeOffset.Now;
while(true)
{
tryMethod();
if(DateTimeOffset.Now.Subtract(startTime).TotalMilliseconds > 5000)
throw new TimeoutException();
}
Using Tasks for custom timeout on Async method
Here my implementation of a custom class with a method to wrap a task to have a timeout.
public class TaskWithTimeoutWrapper
{
protected volatile bool taskFinished = false;
public async Task<T> RunWithCustomTimeoutAsync<T>(int millisecondsToTimeout, Func<Task<T>> taskFunc, CancellationTokenSource cancellationTokenSource = null)
{
this.taskFinished = false;
var results = await Task.WhenAll<T>(new List<Task<T>>
{
this.RunTaskFuncWrappedAsync<T>(taskFunc),
this.DelayToTimeoutAsync<T>(millisecondsToTimeout, cancellationTokenSource)
});
return results[0];
}
public async Task RunWithCustomTimeoutAsync(int millisecondsToTimeout, Func<Task> taskFunc, CancellationTokenSource cancellationTokenSource = null)
{
this.taskFinished = false;
await Task.WhenAll(new List<Task>
{
this.RunTaskFuncWrappedAsync(taskFunc),
this.DelayToTimeoutAsync(millisecondsToTimeout, cancellationTokenSource)
});
}
protected async Task DelayToTimeoutAsync(int millisecondsToTimeout, CancellationTokenSource cancellationTokenSource)
{
await Task.Delay(millisecondsToTimeout);
this.ActionOnTimeout(cancellationTokenSource);
}
protected async Task<T> DelayToTimeoutAsync<T>(int millisecondsToTimeout, CancellationTokenSource cancellationTokenSource)
{
await this.DelayToTimeoutAsync(millisecondsToTimeout, cancellationTokenSource);
return default(T);
}
protected virtual void ActionOnTimeout(CancellationTokenSource cancellationTokenSource)
{
if (!this.taskFinished)
{
cancellationTokenSource?.Cancel();
throw new NoInternetException();
}
}
protected async Task RunTaskFuncWrappedAsync(Func<Task> taskFunc)
{
await taskFunc.Invoke();
this.taskFinished = true;
}
protected async Task<T> RunTaskFuncWrappedAsync<T>(Func<Task<T>> taskFunc)
{
var result = await taskFunc.Invoke();
this.taskFinished = true;
return result;
}
}
Then you can call it like this:
await new TaskWithTimeoutWrapper().RunWithCustomTimeoutAsync(10000, () => this.MyTask());
or
var myResult = await new TaskWithTimeoutWrapper().RunWithCustomTimeoutAsync(10000, () => this.MyTaskThatReturnsMyResult());
And you can add a cancellation token if you want to cancel the running async task if it gets to timeout.
Hope it helps
Another way I like to do it:
public class TimeoutAction
{
private Thread ActionThread { get; set; }
private Thread TimeoutThread { get; set; }
private AutoResetEvent ThreadSynchronizer { get; set; }
private bool _success;
private bool _timout;
/// <summary>
///
/// </summary>
/// <param name="waitLimit">in ms</param>
/// <param name="action">delegate action</param>
public TimeoutAction(int waitLimit, Action action)
{
ThreadSynchronizer = new AutoResetEvent(false);
ActionThread = new Thread(new ThreadStart(delegate
{
action.Invoke();
if (_timout) return;
_timout = true;
_success = true;
ThreadSynchronizer.Set();
}));
TimeoutThread = new Thread(new ThreadStart(delegate
{
Thread.Sleep(waitLimit);
if (_success) return;
_timout = true;
_success = false;
ThreadSynchronizer.Set();
}));
}
/// <summary>
/// If the action takes longer than the wait limit, this will throw a TimeoutException
/// </summary>
public void Start()
{
ActionThread.Start();
TimeoutThread.Start();
ThreadSynchronizer.WaitOne();
if (!_success)
{
throw new TimeoutException();
}
ThreadSynchronizer.Close();
}
}
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(10000);
try
{
Task task = Task.Run(() => { methodToTimeoutAfter10Seconds(); }, cts.Token);
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
using (cts.Token.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
{
throw new OperationCanceledException(cts.Token);
}
}
/* Wait until the task is finish or timeout. */
task.Wait();
/* Rest of the code goes here */
}
catch (TaskCanceledException)
{
Console.WriteLine("Timeout");
}
catch (OperationCanceledException)
{
Console.WriteLine("Timeout");
}
catch (Exception ex)
{
Console.WriteLine("Other exceptions");
}
finally
{
cts.Dispose();
}
Using mature library Polly it can be implemented using optimistic (thus CancellationToken based) as follows:
AsyncTimeoutPolicy policy = Policy.TimeoutAsync(60, TimeoutStrategy.Optimistic);
await policy.ExecuteAsync(async cancel => await myTask(cancel), CancellationToken.None);
myTask(cancel) should be of signature Func<CancellationToken, Task> e.g. async Task MyTast(CancellationToken token) {...}

Categories