Say I have an api that takes individual get requests and batch requests:
http://myapiendpoint.com/mysuperitems/1234
and
http://myapiendpoint.com/mysuperitems/1234,2345,456,5677
and in my code I have a method for getting singles:
async Task<mysuperitem> GetSingleItem(int x) {
var endpoint = $"http://myapiendpoint.com/mysuperitems/{x}";
//... calls single request endpoint
}
but what I want to do is pool the single calls into batch calls.
async Task<mysuperitem> GetSingleItem(int x) {
//... pool this request in a queue and retrieve it when batch complete
}
async Task<IEnumerable<mysuperitem> GetMultiItem(IEnumerable<int> ids){
//... gets items and lets single item know it's done
}
how would i batch the calls asynchronously and inform the single call of completion. Thinking something with a ConcurrentQueue and Timer job?
It seems Task.WhenAll is what you need:
async Task<mysuperitem> GetSingleItem(int x)
{
return await ... // calls single request endpoint
}
async Task<IEnumerable<mysuperitem>> GetMultiItem(IEnumerable<int> ids)
{
return await Task.WhenAll(ids.Select(id => GetSingleItem(id)));
}
Yea, you can use a System.Timers with Timer.Interval.
And I'd use a normal Dictionary> to make it simple and to easily map id's to tasks, you're most likely batch all requests from that intervall anyway, so no real need for a queue. And then simply sync the GetSingleItem with the GetMultiItem called from the timer like:
private Dictionary<int,Task<mysuperitem>> _batchbuffer;
private object _lock = new object();
Task<mysuperitem> GetSingleItem(int id) {
lock(_lock) {
return _batchbuffer[id] = new Task<mysuperitem>();
}
}
async Task GetMultiItem(){
Dictionary<int,Task<mysuperitem>> temp;
lock(_lock) {
temp = new Dictionary<int,Task<mysuperitem>>(_batchbuffer);
_batchbuffer.Clear()
}
var batchResults = // do batch request for temp.Keys;
foreach(var result in batchResults)
temp[result.id].complete(result);
}
this is ofc batching to reduce server/network load, if you want to increase client performance that's something different.
this is a first hack attempt:
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using timers = System.Timers;
using System.Threading;
using System.Collections.Concurrent;
namespace MyNamespace
{
class BatchPoolConsumer<TReturn, TArgs>
{
class PoolItem
{
private readonly object _itemWriteLock = new object();
public object ItemWriteLock => _itemWriteLock;
public Task BlockingTask { get; set; }
public TReturn ReturnValue { get; set; }
public Guid BatchId { get; set; }
public bool IsRead { get; set; }
public ManualResetEventSlim Slim { get; set; }
}
private readonly timers.Timer _batchTimer;
private readonly timers.Timer _poolCleanerTimer;
private readonly ConcurrentDictionary<TArgs, PoolItem> _taskPool =
new ConcurrentDictionary<TArgs, PoolItem>();
private readonly Func<IEnumerable<TArgs>, Task<IEnumerable<(TArgs, TReturn)>>> _batchProcessor;
private readonly int _consumerMaxBatchConsumption;
public BatchPoolConsumer(Func<IEnumerable<TArgs>, Task<IEnumerable<(TArgs, TReturn)>>> batchProcessor, TimeSpan interval, int consumerMaxBatchConsumption)
{
_batchProcessor = batchProcessor;
_consumerMaxBatchConsumption = consumerMaxBatchConsumption;
_batchTimer = InitTimer(interval, BatchTimerElapsed);
_poolCleanerTimer = InitTimer(interval, PoolCleanerElapesed);
}
private static timers.Timer InitTimer(TimeSpan interval, Action<object, timers.ElapsedEventArgs> callback)
{
var timer = new timers.Timer(interval.TotalMilliseconds);
timer.Elapsed += (s, e) => callback(s, e);
timer.Start();
return timer;
}
private void PoolCleanerElapesed(object sendedr, timers.ElapsedEventArgs e)
{
var completedKeys = _taskPool
.Where(i => i.Value.IsRead)
.Select(i => i.Key).ToList();
completedKeys.ForEach(k => _taskPool.TryRemove(k, out _));
}
private void BatchTimerElapsed(object sender, timers.ElapsedEventArgs e)
{
_batchTimer.Stop();
var batchId = Guid.NewGuid();
var keys = _taskPool
.Where(i => !i.Value.BlockingTask.IsCompleted && !i.Value.IsRead && i.Value.BatchId == Guid.Empty)
.Take(_consumerMaxBatchConsumption).Select(kvp => kvp.Key);
keys.ToList()
.ForEach(k =>
{
if(_taskPool.TryGetValue(k, out PoolItem item))
{
lock (item.ItemWriteLock)
{
item.BatchId = batchId;
}
}
});
_batchTimer.Start();
if (_taskPool
.Any(pi => pi.Value.BatchId == batchId))
{
Console.WriteLine($"Processing batch {batchId} for {_taskPool.Count(pi => pi.Value.BatchId == batchId)} items");
var results = _batchProcessor(_taskPool
.Where(pi => pi.Value.BatchId == batchId)
.Select(i => i.Key)).Result;
Console.WriteLine($"Completed batch {batchId} for {_taskPool.Count(pi => pi.Value.BatchId == batchId)} items");
results.ToList().ForEach(r =>
{
if(_taskPool.TryGetValue(r.Item1,out PoolItem val))
{
lock (val.ItemWriteLock)
{
val.ReturnValue = r.Item2;
val.Slim.Set();
}
}
});
}
}
public async Task<TReturn> Get(TArgs args)
{
var slim = new ManualResetEventSlim(false);
var task = Task.Run(() =>
{
slim.Wait();
});
var output = new PoolItem
{
BlockingTask = task,
IsRead = false,
Slim = slim
};
_taskPool[args] = output;
await task;
var returnVal = output.ReturnValue;
output.IsRead = true;
return returnVal;
}
}
}
Related
How can I get all classes that implements a specific interface then call a function of that class if a string member of the specific class matches a given one?
Basically what I have is a ICommandHandler interface:
interface ICommandHandler
{
string Command { get; }
Task ExecuteAsync();
}
and a class that implements it:
public class StartCommand : ICommandHandler
{
public string Command { get => "start"; }
public async Task ExecuteAsync()
{
// do something
}
}
What I want to do is to get all the classes that implements the ICommandHandler interface, then verify if the class.Command equals a specific string and if it does then call the ExecuteAsync method.
I've tried using this answer here: https://stackoverflow.com/a/45382386/15306888 but the class.Command is always null
Edit: The answer I got bellow does what I wanted to do:
What I was looking for was a way to use ICommandHandler to allow me to easily gather all the classes inheriting from it and call the ExecuteAsync function instead of having to manually add the methods in the part of the code handling TdLib message events.
So now my project directory looks something like this:
TelegramClient.cs - The place where the classes inheriting from the ICommandHandler are loaded, and where the ExecuteAsync method is called if the Command member matches the Command parsed from the message returned by telegram.
Handlers/ - Base directory for my handlers
CommandHandlers/ - Folder with the ICommandHandler interface and the classes inheriting from it
MessageHandlers/ - Folder with another interface IMessageHandler and the classes inheriting from it
Anyway, in the meantime I've found another answer on a stackoverflow question (Had to scroll a few times) that made it way easier and faster to get multiple handlers without having to repeat the same code over and over again. I've ended by combining the answer linked above with this one: https://stackoverflow.com/a/41650057/15306888
So I ended with a simple method:
public static IEnumerable<T> GetAll<T>()
{
return Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(T).IsAssignableFrom(type))
.Where(type =>
!type.IsAbstract &&
!type.IsGenericType &&
type.GetConstructor(new Type[0]) != null)
.Select(type => (T)Activator.CreateInstance(type))
.ToList();
}
That can be easily used like:
private async Task ProcessMessage(TdApi.Update.UpdateNewMessage message)
{
var command = GetCommand(message.Message);
var textMessage = GetMessageText(message.Message);
if (!String.IsNullOrWhiteSpace(command))
{
var commandHandlers = GetAll<ICommandHandler>();
foreach (var handler in commandHandlers)
{
if (command == handler.Command)
await handler.ExecuteAsync(_client, message.Message);
}
}
else if (!String.IsNullOrWhiteSpace(textMessage))
{
var messageHandlers = GetAll<IMessageHandler>();
foreach (var handler in messageHandlers)
{
var outgoing = handler.Outgoing && message.Message.IsOutgoing;
var incoming = handler.Incoming && !message.Message.IsOutgoing;
if (outgoing || incoming)
{
if (!String.IsNullOrEmpty(handler.Pattern))
{
var match = Regex.Match(textMessage, handler.Pattern);
if (match.Success)
await handler.ExecuteAsync(_client, message.Message);
}
}
}
}
}
How the Interface is actually implemented:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TdLib;
namespace YoutubeDl_Bot.Handlers.CommandHandlers
{
public class StartCommand : ICommandHandler
{
public string Command { get => "/start"; }
public async Task ExecuteAsync(TdClient client, TdApi.Message message)
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Hi! I'm a bot that downloads and sends video and audio files from youtube links and many other supported services");
stringBuilder.AppendLine(String.Empty);
stringBuilder.AppendLine("**Usage:**");
stringBuilder.AppendLine("• Send or forward a text message containing links and I will:");
stringBuilder.AppendLine("• Download the best audio quality available for the video in the speecified link");
stringBuilder.AppendLine("• Download the best video quality available for the video in the speecified link");
stringBuilder.AppendLine("• Send the direct download URL for every link specified in the message");
stringBuilder.AppendLine("• Supported links are available here: https://ytdl-org.github.io/youtube-dl/supportedsites.html");
var formattedText = await client.ExecuteAsync(new TdLib.TdApi.ParseTextEntities { Text = stringBuilder.ToString(), ParseMode = new TdLib.TdApi.TextParseMode.TextParseModeMarkdown() });
await client.ExecuteAsync(new TdLib.TdApi.SendMessage { ChatId = message.ChatId, InputMessageContent = new TdLib.TdApi.InputMessageContent.InputMessageText { Text = formattedText } });
}
}
}
Full TelegramClient class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using TdLib;
using YoutubeDl_Bot.Handlers.CallbackHandlers;
using YoutubeDl_Bot.Handlers.CommandHandlers;
using YoutubeDl_Bot.Handlers.MessageHandlers;
using YoutubeDl_Bot.Settings;
using YoutubeDl_Bot.Utils;
namespace YoutubeDl_Bot
{
class TelegramBotClient
{
private static TdClient _client;
private static TdLib.TdApi.User _me;
private static int _apiId;
private static string _apiHash;
private static string _token;
#if DEBUG
private static readonly int _verbosityLevel = 4;
#else
private static readonly int _verbosityLevel = 0;
#endif
public static bool AuthCompleted = false;
public TelegramBotClient(int apiId, string apiHash)
{
_apiId = apiId;
_apiHash = apiHash;
}
public async Task<TdClient> CreateClient()
{
_client = new TdClient();
await _client.ExecuteAsync(new TdApi.SetLogVerbosityLevel { NewVerbosityLevel = _verbosityLevel });
return _client;
}
public void StartListening(string botToken)
{
_token = botToken;
_client.UpdateReceived += _client_UpdateReceived;
}
private async void _client_UpdateReceived(object sender, TdApi.Update update)
{
switch (update)
{
case TdApi.Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(TdApi.AuthorizationState.AuthorizationStateWaitTdlibParameters):
await _client.ExecuteAsync(new TdApi.SetTdlibParameters
{
Parameters = new TdApi.TdlibParameters
{
ApiId = _apiId,
ApiHash = _apiHash,
ApplicationVersion = "0.0.1",
DeviceModel = "Bot",
SystemLanguageCode = "en",
SystemVersion = "Unknown"
}
});
break;
case TdApi.Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(TdLib.TdApi.AuthorizationState.AuthorizationStateWaitEncryptionKey):
await _client.ExecuteAsync(new TdLib.TdApi.CheckDatabaseEncryptionKey());
break;
case TdLib.TdApi.Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(TdLib.TdApi.AuthorizationState.AuthorizationStateWaitPhoneNumber):
await _client.ExecuteAsync(new TdLib.TdApi.CheckAuthenticationBotToken { Token = _token });
break;
case TdLib.TdApi.Update.UpdateConnectionState updateConnectionState when updateConnectionState.State.GetType() == typeof(TdLib.TdApi.ConnectionState.ConnectionStateReady):
// To Do Settings
var botSettings = new BotSettings(_apiId, _apiHash, _token);
_me = await _client.ExecuteAsync(new TdLib.TdApi.GetMe());
Helpers.Print($"Logged in as: {_me.FirstName}");
SettingsManager.Set<BotSettings>("BotSettings.data", botSettings);
break;
case TdLib.TdApi.Update.UpdateNewMessage message:
if (!message.Message.IsOutgoing)
await ProcessMessage(message);
break;
case TdApi.Update.UpdateNewCallbackQuery callbackQuery:
await ProcessCallbackQuery(callbackQuery);
break;
default:
break;
}
}
#region PROCESS_MESSAGE
private async Task ProcessMessage(TdApi.Update.UpdateNewMessage message)
{
var command = GetCommand(message.Message);
var textMessage = GetMessageText(message.Message);
#region COMMAND_HANDLERS
if (!String.IsNullOrWhiteSpace(command))
{
var commandHandlers = GetAll<ICommandHandler>();
foreach (var handler in commandHandlers)
{
if (command == handler.Command)
await handler.ExecuteAsync(_client, message.Message);
}
}
#endregion
#region MESSAGE_HANDLERS
else if (!String.IsNullOrWhiteSpace(textMessage))
{
var messageHandlers = GetAll<IMessageHandler>();
foreach (var handler in messageHandlers)
{
var outgoing = handler.Outgoing && message.Message.IsOutgoing;
var incoming = handler.Incoming && !message.Message.IsOutgoing;
if (outgoing || incoming)
{
if (!String.IsNullOrEmpty(handler.Pattern))
{
var match = Regex.Match(textMessage, handler.Pattern);
if (match.Success)
await handler.ExecuteAsync(_client, message.Message);
}
}
}
}
#endregion
}
#endregion
#region PROCESS_CALLACK
private async Task ProcessCallbackQuery(TdApi.Update.UpdateNewCallbackQuery callbackQuery)
{
if (callbackQuery.Payload.GetType() == typeof(TdApi.CallbackQueryPayload.CallbackQueryPayloadData))
{
var payload = callbackQuery.Payload as TdApi.CallbackQueryPayload.CallbackQueryPayloadData;
var callbackHandlers = GetAll<ICallbackHandler>();
foreach (var handler in callbackHandlers)
{
if (handler.DataIsRegex)
if (Regex.Match(System.Text.Encoding.UTF8.GetString(payload.Data), handler.Data).Success)
await handler.ExecuteAsync(_client, callbackQuery);
else if (handler.Data == System.Text.Encoding.UTF8.GetString(payload.Data))
await handler.ExecuteAsync(_client, callbackQuery);
}
}
}
#endregion
#region COMMAND_PARSER
public string GetCommand(TdApi.Message message)
{
string command = null;
TdLib.TdApi.FormattedText formattedText = new TdLib.TdApi.FormattedText();
if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageText))
{
var messageText = message.Content as TdLib.TdApi.MessageContent.MessageText;
formattedText = messageText.Text;
}
else
{
if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessagePhoto))
{
var messagePhoto = message.Content as TdLib.TdApi.MessageContent.MessagePhoto;
formattedText = messagePhoto.Caption;
}
else if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageDocument))
{
var messageDocument = message.Content as TdLib.TdApi.MessageContent.MessageDocument;
formattedText = messageDocument.Caption;
}
else if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageVideo))
{
var messageVideo = message.Content as TdLib.TdApi.MessageContent.MessageVideo;
formattedText = messageVideo.Caption;
}
}
foreach (var entity in formattedText.Entities)
{
if (entity.Type.GetType() == typeof(TdLib.TdApi.TextEntityType.TextEntityTypeBotCommand) && String.IsNullOrWhiteSpace(command))
{
if (entity.Offset == 0)
{
var splitCommand = formattedText.Text.Split();
if (splitCommand[0].EndsWith($"#{_me.Username}"))
{
command = splitCommand[0].Split('#')[0];
}
else
{
command = splitCommand[0];
}
}
}
}
return command;
}
#endregion
#region MESSAGE_PARSER
public string GetMessageText(TdApi.Message message)
{
TdLib.TdApi.FormattedText formattedText = new TdLib.TdApi.FormattedText();
if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageText))
{
var messageText = message.Content as TdLib.TdApi.MessageContent.MessageText;
formattedText = messageText.Text;
}
else
{
if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessagePhoto))
{
var messagePhoto = message.Content as TdLib.TdApi.MessageContent.MessagePhoto;
formattedText = messagePhoto.Caption;
}
else if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageDocument))
{
var messageDocument = message.Content as TdLib.TdApi.MessageContent.MessageDocument;
formattedText = messageDocument.Caption;
}
else if (message.Content.GetType() == typeof(TdLib.TdApi.MessageContent.MessageVideo))
{
var messageVideo = message.Content as TdLib.TdApi.MessageContent.MessageVideo;
formattedText = messageVideo.Caption;
}
}
return formattedText.Text;
}
#endregion
#region REFLECTION
// https://stackoverflow.com/a/41650057/15306888
public static IEnumerable<T> GetAll<T>()
{
return Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(T).IsAssignableFrom(type))
.Where(type =>
!type.IsAbstract &&
!type.IsGenericType &&
type.GetConstructor(new Type[0]) != null)
.Select(type => (T)Activator.CreateInstance(type))
.ToList();
}
#endregion
}
}
Since it's always null I think that the problem is that you're not creating an instance of your handler. I prepared a demo for you where I did that and it works.
public interface ICommandHandler
{
string Command { get; }
Task ExecuteAsync();
}
public class FirstCommandHandler : ICommandHandler
{
public string Command => "First";
public async Task ExecuteAsync()
{
Console.WriteLine("Hello from first.");
await Task.Delay(10);
}
}
public class SecondCommandHandler : ICommandHandler
{
public string Command => "Second";
public async Task ExecuteAsync()
{
Console.WriteLine("Hello from second.");
await Task.Delay(10);
}
}
public class Program
{
static async Task Main(string[] args)
{
var handlers = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(ICommandHandler).IsAssignableFrom(p) && p.IsClass);
foreach (var handler in handlers)
{
var handlerInstance = (ICommandHandler)Activator.CreateInstance(handler);
if (handlerInstance.Command == "First")
{
await handlerInstance.ExecuteAsync();
}
}
}
}
If it's not the case, could you show some more code? Are you trying to check Command value by reflection?
I'm getting the following error on my C# Web API: "Exception thrown: 'System.Threading.ThreadAbortException' in System.Data.dll
Thread was being aborted". I have a long running process on one thread using my data access logic class to get and update records being process. Meanwhile a user submits another group to process which has need of the same data access logic class, thus resulting in the error. Here is a rough sketch of what I'm doing.
WebAPI Class:
public IHttpActionResult OkToProcess(string groupNameToProcess)
{
var logic = GetLogic();
//Gets All Unprocessed Records and Adds them to Blocking Queue
Task.Factory.StartNew(() => dataAccessLogic.LoadAndProcess(groupNameToProcess);
}
public IHttpActionResult AddToProcess(int recordIdToProcess)
{
StaticProcessingFactory.AddToQueue(recordIdToProcess);
}
StaticProcessingFactory
internal static ConcurrentDictionary<ApplicationEnvironment, Logic> correctors = new ConcurrentDictionary<ApplicationEnvironment, Logic>();
internal static BlockingCollection<CorrectionMessage> MessageQueue = new BlockingCollection<Message>(2000);
public void StartService(){
Task.Factory.StartNew(() => LoadService());
}
public void LoadService(){
var logic = GetLogic();
if(isFirstGroupOkToProcessAsPerTextFileLog())
logic.LoadAndProcess("FirstGroup");
if(isSeconddGroupOkToProcessAsPerTextFileLog())
logic.LoadAndProcess("SecondGroup");
}
public static GetLogic(){
var sqlConnectionFactory = Tools.GetSqlConnectionFactory();
string environment = ConfigurationManager.AppSettings["DefaultApplicationEnvironment"];
ApplicationEnvironment applicationEnvironment =
ApplicationEnvironmentExtensions.ToApplicationEnvironment(environment);
return correctors.GetOrAdd(applicationEnvironment, new Logic(sqlConnectionFactory ));
}
public static void AddToQueue(Message message, bool completeAdding = true)
{
if (MessageQueue.IsAddingCompleted)
MessageQueue = new BlockingCollection<Message>();
if (completeAdding && message.ProcessImmediately)
StartQueue(message);
else
MessageQueue.Add(message);
}
public static void StartQueue(Message message = null)
{
if (message != null)
{
if(!string.IsNullOrEmpty(message.ID))
MessageQueue.Add(message);
Logic logic = GetLogic(message.Environment);
try
{
var messages = MessageQueue.TakeWhile(x => logic.IsPartOfGroup(x.GroupName, message.GroupName));
if (messages.Count() > 0)
MessageQueue.CompleteAdding();
int i = 0;
foreach (var msg in messages)
{
i++;
Process(msg);
}
}
catch (InvalidOperationException) { MessageQueue.CompleteAdding(); }
}
}
public static void Process(Message message)
{
Var logic = GetLogic(message.Environment);
var record = logic.GetRecord(message.ID);
record.Status = Status.Processed;
logic.Save(record);
}
Logic Class
private readonly DataAccess DataAccess;
public Logic(SqlConnectionFactory factory)
{
DataAccess = new DataAcess(factory);
}
public void LoadAndProcess(string groupName)
{
var groups = DataAccess.GetGroups();
var records = DataAccess.GetRecordsReadyToProcess(groups);
for(int i = 0; i < records.Count; i++)
{
Message message = new Message();
message.Enviornment = environment.ToString();
message.ID = records[i].ID;
message.User = user;
message.Group = groupName;
message.ProcessImmediately = true;
StaticProcessingFactory.AddToQueue(message, i + 1 == records.Count);
}
}
Any ideas how I might ensure that all traffic from all threads have access to the Data Access Logic without threads being systematically aborted?
I have thread pool implementation where whenever I try to stop/join the pool there is always one random thread in the pool that will not stop (state == Running) when I call Stop() on the pool.
I cannot see why, I only have one lock, I notify whoever might be blocked waiting for Dequeue with Monitor.PulseAll in Stop. The debugger clearly shows most of them got the message, it is just always 1 out of N that is still running...
Here is a minimal implementation of the pool
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MultiThreading
{
public class WorkerHub
{
private readonly object _listMutex = new object();
private readonly Queue<TaskWrapper> _taskQueue;
private readonly List<Thread> _threads;
private int _runCondition;
private readonly Dictionary<string, int> _statistics;
public WorkerHub(int count = 4)
{
_statistics = new Dictionary<string, int>();
_taskQueue = new Queue<TaskWrapper>();
_threads = new List<Thread>();
InitializeThreads(count);
}
private bool ShouldRun
{
get => Interlocked.CompareExchange(ref _runCondition, 1, 1) == 1;
set
{
if (value)
Interlocked.CompareExchange(ref _runCondition, 1, 0);
else
Interlocked.CompareExchange(ref _runCondition, 0, 1);
}
}
private void InitializeThreads(int count)
{
Action threadHandler = () =>
{
while (ShouldRun)
{
var wrapper = Dequeue();
if (wrapper != null)
{
wrapper.FunctionBinding.Invoke();
_statistics[Thread.CurrentThread.Name] += 1;
}
}
};
for (var i = 0; i < count; ++i)
{
var t = new Thread(() => { threadHandler.Invoke(); });
t.Name = $"WorkerHub Thread#{i}";
_statistics[t.Name] = 0;
_threads.Add(t);
}
}
public Task Enqueue(Action work)
{
var tcs = new TaskCompletionSource<bool>();
var wrapper = new TaskWrapper();
Action workInvoker = () =>
{
try
{
work.Invoke();
tcs.TrySetResult(true);
}
catch (Exception e)
{
tcs.TrySetException(e);
}
};
Action workCanceler = () => { tcs.TrySetCanceled(); };
wrapper.FunctionBinding = workInvoker;
wrapper.CancelBinding = workCanceler;
lock (_taskQueue)
{
_taskQueue.Enqueue(wrapper);
Monitor.PulseAll(_taskQueue);
}
return tcs.Task;
}
private TaskWrapper Dequeue()
{
lock (_listMutex)
{
while (_taskQueue.Count == 0)
{
if (!ShouldRun)
return null;
Monitor.Wait(_listMutex);
}
_taskQueue.TryDequeue(out var wrapper);
return wrapper;
}
}
public void Stop()
{
ShouldRun = false;
//Wake up whoever is waiting for dequeue
lock (_listMutex)
{
Monitor.PulseAll(_listMutex);
}
foreach (var thread in _threads)
{
thread.Join();
}
var sum = _statistics.Sum(pair => pair.Value) * 1.0;
foreach (var stat in _statistics)
{
Console.WriteLine($"{stat.Key} ran {stat.Value} functions, {stat.Value/sum * 100} percent of the total.");
}
}
public void Start()
{
ShouldRun = true;
foreach (var thread in _threads) thread.Start();
}
}
}
With a test run
public static async Task Main(string[] args)
{
var hub = new WorkerHub();
var tasks = Enumerable.Range(0, (int) 100).Select(x => hub.Enqueue(() => Sum(x)))
.ToArray();
var sw = new Stopwatch();
sw.Start();
hub.Start();
await Task.WhenAll(tasks);
hub.Stop();
sw.Start();
Console.WriteLine($"Work took: {sw.ElapsedMilliseconds}ms.");
}
public static int Sum(int n)
{
var sum = 0;
for (var i = 0; i <= n; ++i) sum += i;
Console.WriteLine($"Sum of numbers up to {n} is {sum}");
return sum;
}
Am I missing something fundamental? Please note this is not production code (phew) but stuff I am just missing around with so you might find more than 1 issue :)
I wasn't able to repro your MCVE at first because I ran it in a non-async Main()...
If you view the 'Threads' debug window at the call to hub.Stop(); you should see that execution has switched to one of your worker threads. This is why one worker thread does not respond.
I think its related to the problem described here.
Switching Enqueue(Action work) to use TaskCreationOptions.RunContinuationsAsynchronously should fix it:
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
[Edit]
Probably a better way to avoid the problem is to swap out the direct thread management to use tasks (this isn't a proper drop-in replacement for your current code, just want to show the idea):
public class TaskWorkerHub
{
ConcurrentQueue<Action> workQueue = new ConcurrentQueue<Action>();
int concurrentTasks;
CancellationTokenSource cancelSource;
List<Task> workers = new List<Task>();
private async Task Worker(CancellationToken cancelToken)
{
while (workQueue.TryDequeue(out var workTuple))
{
await Task.Run(workTuple, cancelToken);
}
}
public TaskWorkerHub(int concurrentTasks = 4)
{
this.concurrentTasks = concurrentTasks;
}
public void Enqueue(Action work) => workQueue.Enqueue(work);
public void Start()
{
cancelSource = new CancellationTokenSource();
for (int i = 0; i < concurrentTasks; i++)
{
workers.Add(Worker(cancelSource.Token));
}
}
public void Stop() => cancelSource.Cancel();
public Task WaitAsync() => Task.WhenAll(workers);
}
I have had a static version of this type of code working in a static version. However the API calls were just incredibly slow. I am trying to move to asynchronous now that C# 7 supports console async tasks (where I add code to connect to my DB and store data. I want to see this code output on the console to ensure it's working so I can assign variables for loading. I can't seem to figure out how to access the list from main. Here is the code I have so far:
Wrapper (or C# library):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace AlphaVantageApiWrapper
{
public static class AlphaVantageApiWrapper
{
public static async Task<AlphaVantageRootObject> GetTechnical(List<ApiParam> parameters, string apiKey)
{
var stringRequest = parameters.Aggregate(#"https://www.alphavantage.co/query?", (current, param) => current + param.ToApiString());
stringRequest += "&apikey=" + apiKey;
var apiData = await CallAlphaVantageApi(stringRequest);
var technicalsObject = new AlphaVantageRootObject
{
MetaData = new MetaData
{
Function = parameters.FirstOrDefault(x => x.ParamName.Equals("function"))?.ParamValue ?? "NA?",
Interval = parameters.FirstOrDefault(x => x.ParamName.Equals("interval"))?.ParamValue ?? "NA?",
SeriesType = parameters.FirstOrDefault(x => x.ParamName.Equals("series_type"))?.ParamValue ?? "NA?",
Symbol = parameters.FirstOrDefault(x => x.ParamName.Equals("symbol"))?.ParamValue ?? "NA?"
},
TechnicalsByDate = apiData.Last.Values().OfType<JProperty>().Select(x => new TechnicalDataDate
{
Date = Convert.ToDateTime(x.Name),
Data = x.Value.OfType<JProperty>().Select(r => new TechnicalDataObject
{
TechnicalKey = r.Name,
TechnicalValue = Convert.ToDouble(r.Value.ToString())
}).ToList()
})
.ToList()
};
return technicalsObject;
}
public class ApiParam
{
public string ParamName;
public string ParamValue;
public ApiParam(string paramNameIn, string paramValueIn)
{
ParamName = paramNameIn;
ParamValue = paramValueIn;
}
public string ToApiString()
{
return $"&{ParamName}={ParamValue}";
}
}
public static string ToDescription(this Enum enumeration)
{
var type = enumeration.GetType();
var memInfo = type.GetMember(enumeration.ToString());
if (memInfo.Length <= 0) return enumeration.ToString();
var attrs = memInfo[0].GetCustomAttributes(typeof(EnumDescription), false);
return attrs.Length > 0 ? ((EnumDescription)attrs[0]).Text : enumeration.ToString();
}
public static async Task<JObject> CallAlphaVantageApi(string stringRequest)
{
try
{
using (var client = new HttpClient())
{
var res = await client.GetStringAsync(stringRequest);
return JsonConvert.DeserializeObject<JObject>(res);
}
}
catch (Exception e)
{
//fatal error
return null;
}
}
public class AlphaVantageRootObject
{
public MetaData MetaData;
public List<TechnicalDataDate> TechnicalsByDate;
}
public class MetaData
{
public string Function;
public string Interval;
public string SeriesType;
public string Symbol;
}
public class TechnicalDataDate
{
public DateTime Date;
public List<TechnicalDataObject> Data;
}
public class TechnicalDataObject
{
public string TechnicalKey { get; set; }
public double TechnicalValue { get; set; }
}
public class EnumDescription : Attribute
{
public string Text { get; }
public EnumDescription(string text)
{
Text = text;
}
}
public enum AvFuncationEnum
{
[EnumDescription("SMA")] Sma,
[EnumDescription("EMA")] Ema,
[EnumDescription("MACD")] Macd,
[EnumDescription("STOCH")] Stoch,
[EnumDescription("RSI")] Rsi,
}
public enum AvIntervalEnum
{
[EnumDescription("1min")] OneMinute,
[EnumDescription("5min")] FiveMinutes,
[EnumDescription("15min")] FifteenMinutes,
[EnumDescription("30min")] ThirtyMinutes,
[EnumDescription("60min")] SixtyMinutes,
[EnumDescription("daily")] Daily,
[EnumDescription("weekly")] Weekly,
[EnumDescription("monthly")] Monthly
}
public enum AvSeriesType
{
[EnumDescription("close")] Close,
[EnumDescription("open")] Open,
[EnumDescription("high")] High,
[EnumDescription("low")] Low,
}
}
}
`
The c# async main task (which obviously isn't working)...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AlphaVantageApiWrapper.Test
{
public static class AlphaVantageApiDbLoader
{
public static async Task Main(string[] args)
{
var API_KEY = "EnterAPIHERE";
var StockTickers = new List<string> { "AAPL" }; //eventualy becomes a list pulled in from the DB for processing
foreach (var ticker in StockTickers)
{
var parameters = new List<AlphaVantageApiWrapper.ApiParam>
{
new AlphaVantageApiWrapper.ApiParam("function", AlphaVantageApiWrapper.AvFuncationEnum.Sma.ToDescription()),
new AlphaVantageApiWrapper.ApiParam("symbol", ticker),
new AlphaVantageApiWrapper.ApiParam("interval", AlphaVantageApiWrapper.AvIntervalEnum.Daily.ToDescription()),
new AlphaVantageApiWrapper.ApiParam("time_period", "5"),
new AlphaVantageApiWrapper.ApiParam("series_type", AlphaVantageApiWrapper.AvSeriesType.Open.ToDescription()),
};
//Start Collecting SMA values
var SMA_5 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
///var SMA_5Result = AlphaVantageApiWrapper.TechnicalDataObject() // can't all method error just want values fron list
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "20";
var SMA_20 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "50";
var SMA_50 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
parameters.FirstOrDefault(x => x.ParamName == "time_period").ParamValue = "200";
var SMA_200 = await AlphaVantageApiWrapper.GetTechnical(parameters, API_KEY);
//Change function to EMA
//Change function to RSI
//Change function to MACD
}
}
}
}
Any help would be greatly appreciated! I know the code runs in the background, I just can't seem to get it to a point to view it on the console screen. Eventually I would assign the symbol, date, value returned variable and read these to a DB. I'm used to using DataTables, but the async and .ToList is new to me. Thanks!!
We are using C1 Azure Redis Cache in our application. Recently we are experiencing lots of time-outs on GET operations.
According to this article, one of possible solutions is to implement pool of ConnectionMultiplexer objects.
Another possible solution is to use a pool of ConnectionMultiplexer
objects in your client, and choose the “least loaded”
ConnectionMultiplexer when sending a new request. This should prevent
a single timeout from causing other requests to also timeout.
How would implementation of a pool of ConnectionMultiplexer objects using C# look like?
Edit:
Related question that I asked recently.
You can also accomplish this in a easier way by using StackExchange.Redis.Extensions
Sample code:
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Core.Abstractions;
using StackExchange.Redis.Extensions.Core.Configuration;
using System;
using System.Collections.Concurrent;
using System.Linq;
namespace Pool.Redis
{
/// <summary>
/// Provides redis pool
/// </summary>
public class RedisConnectionPool : IRedisCacheConnectionPoolManager
{
private static ConcurrentBag<Lazy<ConnectionMultiplexer>> connections;
private readonly RedisConfiguration redisConfiguration;
public RedisConnectionPool(RedisConfiguration redisConfiguration)
{
this.redisConfiguration = redisConfiguration;
Initialize();
}
public IConnectionMultiplexer GetConnection()
{
Lazy<ConnectionMultiplexer> response;
var loadedLazys = connections.Where(lazy => lazy.IsValueCreated);
if (loadedLazys.Count() == connections.Count)
{
response = connections.OrderBy(x => x.Value.GetCounters().TotalOutstanding).First();
}
else
{
response = connections.First(lazy => !lazy.IsValueCreated);
}
return response.Value;
}
private void Initialize()
{
connections = new ConcurrentBag<Lazy<ConnectionMultiplexer>>();
for (int i = 0; i < redisConfiguration.PoolSize; i++)
{
connections.Add(new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(redisConfiguration.ConfigurationOptions)));
}
}
public void Dispose()
{
var activeConnections = connections.Where(lazy => lazy.IsValueCreated).ToList();
activeConnections.ForEach(connection => connection.Value.Dispose());
Initialize();
}
}
}
Where RedisConfiguration is something like this:
return new RedisConfiguration()
{
AbortOnConnectFail = true,
Hosts = new RedisHost[] {
new RedisHost()
{
Host = ConfigurationManager.AppSettings["RedisCacheAddress"].ToString(),
Port = 6380
},
},
ConnectTimeout = Convert.ToInt32(ConfigurationManager.AppSettings["RedisTimeout"].ToString()),
Database = 0,
Ssl = true,
Password = ConfigurationManager.AppSettings["RedisCachePassword"].ToString(),
ServerEnumerationStrategy = new ServerEnumerationStrategy()
{
Mode = ServerEnumerationStrategy.ModeOptions.All,
TargetRole = ServerEnumerationStrategy.TargetRoleOptions.Any,
UnreachableServerAction = ServerEnumerationStrategy.UnreachableServerActionOptions.Throw
},
PoolSize = 50
};
If you're using StackExchange.Redis, according to this github issue, you can use the TotalOutstanding property on the connection multiplexer object.
Here is a implementation I came up with, that is working correctly:
public static int POOL_SIZE = 100;
private static readonly Object lockPookRoundRobin = new Object();
private static Lazy<Context>[] lazyConnection = null;
//Static initializer to be executed once on the first call
private static void InitConnectionPool()
{
lock (lockPookRoundRobin)
{
if (lazyConnection == null) {
lazyConnection = new Lazy<Context>[POOL_SIZE];
}
for (int i = 0; i < POOL_SIZE; i++){
if (lazyConnection[i] == null)
lazyConnection[i] = new Lazy<Context>(() => new Context("YOUR_CONNECTION_STRING", new CachingFramework.Redis.Serializers.JsonSerializer()));
}
}
}
private static Context GetLeastLoadedConnection()
{
//choose the least loaded connection from the pool
/*
var minValue = lazyConnection.Min((lazyCtx) => lazyCtx.Value.GetConnectionMultiplexer().GetCounters().TotalOutstanding);
var lazyContext = lazyConnection.Where((lazyCtx) => lazyCtx.Value.GetConnectionMultiplexer().GetCounters().TotalOutstanding == minValue).First();
*/
// UPDATE following #Luke Foust comment below
Lazy<Connection> lazyContext;
var loadedLazys = lazyConnection.Where((lazy) => lazy.IsValueCreated);
if(loadedLazys.Count()==lazyConnection.Count()){
var minValue = loadedLazys.Min((lazy) => lazy.Value.TotalOutstanding);
lazyContext = loadedLazys.Where((lazy) => lazy.Value.TotalOutstanding == minValue).First();
}else{
lazyContext = lazyConnection[loadedLazys.Count()];
}
return lazyContext.Value;
}
private static Context Connection
{
get
{
lock (lockPookRoundRobin)
{
return GetLeastLoadedConnection();
}
}
}
public RedisCacheService()
{
InitConnectionPool();
}