My telegram bot is necessary so that the user can answer questions in order and save these answers in the same order for a specific user in parallel.
static readonly ConcurrentDictionary<int, string[]> Answers = new ConcurrentDictionary<int, string[]>();
static void Main(string[] args)
{
try
{
Task t1 = CreateHostBuilder(args).Build().RunAsync();
Task t2 = BotOnMessage();
await Task.WhenAll(t1, t2);
}
catch (Exception ex)
{
Console.WriteLine("Error" + ex);
}
}
here is my BotOnMessage() method to receive and process messages from users
async static Task BotOnMessage()
{
int offset = 0;
int timeout = 0;
try
{
await bot.SetWebhookAsync("");
while (true)
{
var updates = await bot.GetUpdatesAsync(offset, timeout);
foreach (var update in updates)
{
var message = update.Message;
if (message.Text == "/start")
{
Registration(message.Chat.Id.ToString(), message.Chat.FirstName.ToString(), createdDateNoTime.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture));
var replyKeyboard = new ReplyKeyboardMarkup
{
Keyboard = new[]
{
new[]
{
new KeyboardButton("eng"),
new KeyboardButton("ger")
},
}
};
replyKeyboard.OneTimeKeyboard = true;
await bot.SendTextMessageAsync(message.Chat.Id, "choose language", replyMarkup: replyKeyboard);
}
switch (message.Text)
{
case "eng":
var replyKeyboardEN = new ReplyKeyboardMarkup
{
Keyboard = new[]
{
new[]
{
new KeyboardButton("choice1"),
new KeyboardButton("choice2")
},
}
};
replyKeyboardEN.OneTimeKeyboard = true;
await bot.SendTextMessageAsync(message.Chat.Id, "Enter choice", replyMarkup: replyKeyboardEN);
await AnonymEN();
break;
case "ger":
var replyKeyboardGR = new ReplyKeyboardMarkup
{
Keyboard = new[]
{
new[]
{
new KeyboardButton("choice1.1"),
new KeyboardButton("choice2.2")
},
}
};
replyKeyboardGR.OneTimeKeyboard = true;
await bot.SendTextMessageAsync(message.Chat.Id, "Enter choice", replyMarkup: replyKeyboardGR);
await AnonymGR();
break;
}
offset = update.Id + 1;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error" + ex);
}
}
and AnonymEN() method for eng case in switch. The problem appears here when I call this method from switch case in BotOnMessage(). Until switch (message.Text) multiple users can asynchronously send messages and get response. When first user enters AnonymEN() second user can't get response from this method until first user will finish it till the end. Also I call BotOnMessage() in the end of AnonymEN() to get back for initial point with possibility to start bot again. For the ordered structure of questions and answers I used ConcurrentDictionary way from here Save user messages sent to bot and send finished form to other user. Any suggestion and solution how to edit code to make this bot available for multiple users at one time?
async static Task AnonymEN()
{
int offset = 0;
int timeout = 0;
try
{
await bot.SetWebhookAsync("");
while (true)
{
var updates = await bot.GetUpdatesAsync(offset, timeout);
foreach (var update in updates)
{
var message = update.Message;
int userId = (int)message.From.Id;
if (message.Type == MessageType.Text)
{
if (Answers.TryGetValue(userId, out string[] answers))
{
var title = message.Text;
if (answers[0] == null)
{
answers[0] = message.Text;
await bot.SendTextMessageAsync(message.Chat, "Enter age");
}
else
{
SaveMessage(message.Chat.Id.ToString(), "anonym", "anonym", "anonym", answers[0].ToString(), title.ToString(), createdDateNoTime.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture));
Answers.TryRemove(userId, out string[] _);
await bot.SendTextMessageAsync(message.Chat.Id, "ty for request click /start");
await BotOnMessage();
}
}
else if (message.Text == "choice1")
{
Answers.TryAdd(userId, new string[1]);
await bot.SendTextMessageAsync(message.Chat.Id, "Enter name");
}
}
offset = update.Id + 1;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error" + ex);
}
}
I can see multiple issues with your code:
It is hard to read. While this is a personal preference I strongly advise to write short concise methods that have 1 responsibility. This will make it easier to understand and maintain your code. https://en.wikipedia.org/wiki/Single-responsibility_principle
Everything is static. This makes it very hard to keep track of any state such as language that should be tracked per user.
Using infinite loops and recursion with no escape. I highly doubt this was intended but you could get an infinite chain of calls like this BotOnMessage -> AnonymEN -> BotOnMessage -> AnonymEN. I think you want to exit the AnonymEN function using either a return, break or while(someVar) approach instead of calling the BotOnMessage function.
If two users are sending messages you get mixed responses. Example message flow user1: /start, user1: eng, user2: hello. The bot will now give an english response to user2. I'm sure this is not intended
The code below is a minimal example that addresses the issues I mentioned. It is not perfect code but should help you get started.
private Dictionaty<string, UserSession> userSessions = new ();
async Task BotOnMessage()
{
try
{
while(true)
{
var message = await GetMessage(timeout);
var userSession = GetUserSession(message.user);
userSession.ProcessMessage(message);
}
}
catch(){}
}
async void GetUserSession(string user)
{
if(!userSessions.HasKey(user))
{
userSessions[user](new Session());
}
return userSessions[user];
}
public class UserSession
{
public async Task ProcessMessage(message)
{
// Existing message processing code goes here.
// Do not use a loop or recursion.
// Instead track the state (e.g. languge) using fields.
}
}
I have an async operation that takes in an List of Airline as parameter and returns some data, and I have a list of Airlines for which I want to get data for.
However, if I can't get the data for all those Airlines after some predefined amount of time, I want to stop waiting and return something else to the user.
public async Task Run()
{
var watch = System.Diagnostics.Stopwatch.StartNew();
await RunAirline();
watch.Stop();
Console.WriteLine($"Total Execution Time: {watch.ElapsedMilliseconds + Environment.NewLine}");
//return $"Total Execution Time: {watch.ElapsedMilliseconds + Environment.NewLine}";
//Console.ReadLine();
}
private static async Task RunAirline()
{
try
{
List<string> AirlineList = GetAirLineCodes();
List<Task<WebsiteDataModel.WebsiteDataModel>> taskList = new List<Task<WebsiteDataModel.WebsiteDataModel>>();
foreach (string AirlineCode in AirlineList)
{
taskList.Add(Task.Run(() => CallindividualAirline(AirlineCode)));
}
var result = await Task.WhenAll(taskList);
foreach (WebsiteDataModel.WebsiteDataModel model in result)
{
Display(model);
}
}
catch (Exception Ex)
{
Console.WriteLine(Ex.Message.ToString());
}
}
private static List<string> GetAirLineCodes()
{
return new List<string>()
{
"A",
"B",
"C"
};
}
private static void Display(WebsiteDataModel.WebsiteDataModel result)
{
Console.WriteLine($"Website Content as {result.DataContent} , Website Name as : {result.WebsiteName} Status as : {result.Status} , Content length as : {result.WebsiteData.Length} ----- Error as : {result.error.FaultException.ToString()}." + Environment.NewLine);
}
private static WebsiteDataModel.WebsiteDataModel CallindividualAirline(string AirlineCode)
{
WebsiteDataModel.WebsiteDataModel LobjWebsiteDataModel = new WebsiteDataModel.WebsiteDataModel();
WebsiteDataModel.ErrorData LobjErrorData = new WebsiteDataModel.ErrorData();
try
{
switch (AirlineCode)
{
// calling Airline API...........
case "A":
ClsAirOne LobjAirOne = new ClsAirOne();
LobjWebsiteDataModel = LobjAirOne.GetAirDataData("https://book.xxxxx.com");
return LobjWebsiteDataModel;
case "B":
ClsAirTwo LobjAirTwo = new ClsAirTwo();
LobjWebsiteDataModel = LobjAirTwo.GetAirData("https://book.xxxxx.in");
return LobjWebsiteDataModel;
case "C":
ClsAirThree LobjAirThree = new ClsAirThree();
LobjWebsiteDataModel = LobjAirThree.GetAirData("https://xxxxx.in/");
return LobjWebsiteDataModel;
default:
return LobjWebsiteDataModel;
}
}
catch (Exception Ex)
{
LobjWebsiteDataModel.Status = "0";
LobjWebsiteDataModel.WebsiteData = "";
LobjErrorData.FaultException = "ERR-01" + Ex.Message.ToString();
LobjWebsiteDataModel.error = LobjErrorData;
return LobjWebsiteDataModel;
}
}
The best way to do this is to cancel each operation passed to the Task.WhenAll. You can create a cancellation token source with a timeout, and then pass its CancellationToken down to the methods that actually do the I/O.
E.g.:
public async Task Run()
{
...
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await RunAirline(cts.Token);
...
}
private static async Task RunAirline(CancellationToken cancellationToken)
{
...
foreach (string AirlineCode in AirlineList)
taskList.Add(Task.Run(() => CallindividualAirline(AirlineCode, cancellationToken)));
...
}
private static WebsiteDataModel.WebsiteDataModel CallindividualAirline(string AirlineCode, CancellationToken cancellationToken)
{
...
ClsAirOne LobjAirOne = new ClsAirOne();
LobjWebsiteDataModel = LobjAirOne.GetAirDataData("https://book.xxxxx.com", cancellationToken);
...
ClsAirTwo LobjAirTwo = new ClsAirTwo();
LobjWebsiteDataModel = LobjAirTwo.GetAirData("https://book.xxxxx.in", cancellationToken);
...
}
I have multiple running tasks in Console application. In my task I call some method from EWS Managed API. So When EWS throw exception code execution is not return to WaitAll.
while (index < messageData.Count)
{
var mess = messageData[index];
var task = new System.Threading.Tasks.Task<EventsCreationResult>(() =>
{
EventsCreationResult taskResult = null;
taskResult = CreateEmail(mess, Service);
return taskResult;
});
task.Start();
tasks.Add(task);
index = index + 1;
}
System.Threading.Tasks.Task.WaitAll(tasks.ToArray());
And Function
public static EventsCreationResult CreateEmail(string folderName, EmailMessage mess)
{
EventsCreationResult result = new EventsCreationResult();
result.Key = mess.Subject;
try
{
mess.Save(FindFolder(folderName).Id);
result.EventsCreationLog = "Message created:" + mess.Subject;
}
catch (Exception ex)
{
result.Error = ex;
}
return result;
}
So how to avoid dead lock and if its possible do not cancel task which not rise error.
I am getting the following error:
Exception Info: System.OutOfMemoryException
Stack:
at System.Threading.ExecutionContext.CreateCopy()
at System.Threading.Tasks.Task.CopyExecutionContext(System.Threading.ExecutionContext)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef)
at System.Threading.Tasks.Task.ExecuteEntry(Boolean)
at System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
The application is a c# windows service (using TopShelf). The app is using HanFire intitiate RunScan() method.
I do not see where in the code, but I suspect it is add to the Blocking Collection.
Code:
public void RunScan(IJobCancellationToken cancellationToken, string path, int pathId)
{
SmartScanDAO scDAO = new SmartScanDAO();
PathInfo RootPathInfo = scDAO.ScanStarted(pathId);
try
{
if (RootPathInfo == null)
{
ErrorLogger.LogEvent(RootPathInfo.Id, string.Format("Path ({1}): {0} is null", path, pathId), EventLogEntryType.Error);
return;
}
if (RootPathInfo.Status == ScanStatus.Processing)
{
ErrorLogger.LogEvent(RootPathInfo.Id, string.Format("Path {0} is currently being scanned", path), EventLogEntryType.Information);
return;
}
RootPathInfo.Status = ScanStatus.Processing;
scDAO.ScanStatus(RootPathInfo);
ErrorLogger.LogEvent(string.Format("Scanning {0}", path), EventLogEntryType.Information);
if (!Directory.Exists(path))
{
scDAO.DisableIsilonScanPathById(RootPathInfo.Id);
ErrorLogger.LogEvent(RootPathInfo.Id, "The Path does not exists: " + path, EventLogEntryType.Error);
return;
}
// Get Directories to Skip
skipPaths = scDAO.GetDuplicateIsilonScanPaths(RootPathInfo.Path);
DirectoryInfo di = new DirectoryInfo(path);
SplunkExport.DeleteFiles(path, new List<string>() { "acl", "path" }, RootPathInfo.Id);
DirectoryType = (DirectoryType)Enum.Parse(typeof(DirectoryType), RootPathInfo.DirectoryType);
RootPathInfo.Path = di.FullName.ToLower();
RootPathInfo.Owner = GetAcl(RootPathInfo.Id, RootPathInfo.Path, DirectoryType, true, false, true);// SecurityUtils.GetOwner(di.FullName, (DirectoryType)Enum.Parse(typeof(DirectoryType), RootPathInfo.DirectoryType));
RootPathInfo.Files = 0;
RootPathInfo.Size = 0;
Interlocked.Add(ref FileCount, di.GetFiles().Length);
Interlocked.Add(ref DirectorySize, (int)di.GetFiles().Sum(f => f.Length));
Task<List<string>> outputMetaDataTask = Task.Factory.StartNew(() => WriteOutput(RootPathInfo.Path, SplunkFileType.MetaData, MetaDataQueue), TaskCreationOptions.LongRunning);
Task<List<string>> outputACLTask = Task.Factory.StartNew(() => WriteOutput(RootPathInfo.Path, SplunkFileType.ACL, ACLQueue), TaskCreationOptions.LongRunning);
Action action = (() => UpdateStats(RootPathInfo.Id, MetaDataQueue, ACLQueue));
CancellationTokenSource UpdateStatsToken = new CancellationTokenSource();
IObservable<long> observable = Observable.Interval(TimeSpan.FromMinutes(10));
// Subscribe the obserable to the task on execution.
observable.Subscribe(x =>
{
Task task = new Task(action); task.Start();
// task.ContinueWith(c => resumeAction());
}, UpdateStatsToken.Token);
MetaDataQueue.Add(string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"", DateTime.UtcNow + " UTC", di.FullName.ToLower(), 1, ((DirectoryInfo)di).GetFiles().Length, string.Format("{0:0.0}", ((DirectoryInfo)di).GetFiles().Sum(f => f.Length) / 1024 / 1024), RootPathInfo.Owner, di.LastAccessTimeUtc, di.CreationTimeUtc, di.LastWriteTimeUtc, ""));
//
// Traverse the path
GetSystemObjects(cancellationToken, di, RootPathInfo.Id, DirectoryType);
// Complete adding
MetaDataQueue.CompleteAdding();
ACLQueue.CompleteAdding();
// wait for
outputMetaDataTask.Wait();
outputACLTask.Wait();
//Send Files to Splunk
SplunkExport.CopyFilesToSplunk(outputMetaDataTask.Result, outputACLTask.Result, RootPathInfo.Id);
SmartScanDAO dao = new SmartScanDAO();
RootPathInfo.Size = DirectorySize;
}
catch (OperationCanceledException cex)
{
RootPathInfo.Status = ScanStatus.Cancelled;
if (scDAO == null)
scDAO = new SmartScanDAO();
scDAO.ScanStatus(RootPathInfo);
ErrorLogger.LogEvent(cex, RootPathInfo.Id);
}
catch (Exception ex)
{
if (RootPathInfo == null)
{
RootPathInfo = new PathInfo();
RootPathInfo.Id = pathId;
}
ErrorLogger.LogEvent(ex, RootPathInfo.Id);
RootPathInfo.Status = ScanStatus.Error;
if (scDAO == null)
scDAO = new SmartScanDAO();
scDAO.ScanStatus(RootPathInfo);
}
}
List<string> WriteOutput(string path, SplunkFileType fileType, BlockingCollection<string> queue)
{
var fileList = new List<string>();
int filecount = 1;
int linecount = 0;
int maxlinecount = 200000;
string header = (fileType.ToString() == SplunkFileType.ACL.ToString()?aclHeader:metaDataHeader);
var filepattern = SplunkExport.GetPaths(path, fileType.ToString());
string filename = string.Format(filepattern, filecount);
fileList.Add(filename);
while (true)
{
using (var strm = File.AppendText(filename))
{
foreach (var s in queue.GetConsumingEnumerable())
{
if (linecount == 0)
strm.WriteLine(header);
strm.WriteLine(s);
// if you want to make sure it's written to disk immediately,
// call Flush. This will slow performance, however.
strm.Flush();
linecount++;
if (linecount > maxlinecount)
{
linecount = 0;
filecount++;
break;
}
}
}
if (queue.IsCompleted)
break;
filename = string.Format(filepattern, filecount);
fileList.Add(filename);
}
return fileList;
}
private void GetSystemObjects(IJobCancellationToken cancellationToken, DirectoryInfo di, int pathid, DirectoryType directorytype = DirectoryType.Share)
{
long files = 0;
long size = 0;
int mb = 1024 * 1024;
try
{
Parallel.ForEach<FileSystemInfo>(di.EnumerateFileSystemInfos("*", System.IO.SearchOption.TopDirectoryOnly).Where(r => !r.FullName.Contains(#"\~snapshot") ), (FileSystemInfo fso) =>
{
if (skipPaths.Contains(fso.FullName))
return;
if (cancellationToken != null)
cancellationToken.ThrowIfCancellationRequested();
bool isDirectory = fso.EntryInfo.IsDirectory;
string owner = "";
owner = GetAcl(pathid, fso.FullName, directorytype, isDirectory, false);
try
{
if (isDirectory)
{
DirectoryInfo dis = new DirectoryInfo(fso.FullName);
lock (lckObject)
{
files = ((DirectoryInfo)fso).GetFiles().Length;
size = ((DirectoryInfo)fso).GetFiles().Sum(f => f.Length);
}
Interlocked.Add(ref FileCount, files);
Interlocked.Add(ref DirectorySize, size);
ErrorLogger.LogEvent(pathid, string.Format("Scan Directory\t{0}\t{1}", fso.FullName, files));
}
else
{
size = ((FileInfo)fso).Length;
files = 0;
}
MetaDataQueue.Add(string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\"", DateTime.UtcNow + " UTC", fso.FullName.ToLower(), (fso.EntryInfo.IsDirectory ? 1 : 0), files, string.Format("{0:0.0}", size / mb), owner, fso.LastAccessTimeUtc, fso.CreationTimeUtc, fso.LastWriteTimeUtc));
}
catch (Exception ex)
{
ErrorLogger.LogEvent(ex, pathid);
}
if (isDirectory)
GetSystemObjects(cancellationToken, (DirectoryInfo)fso, pathid, directorytype);
fso = null;
}); // end of ForEach
}
catch (Exception ex)
{
ErrorLogger.LogEvent(ex, pathid);
}
}
Any suggestions where the error may be occurring or how to get closer to the root cause. In the exception, I don't see any indication where it is.
I have created a task which creates an XML string. The task can last multiple seconds. When the task isn't finished after 5 seconds, I want to cancel the Task 'smootly' and continue with writing the rest of the XML. So I built in cancellation inside my task. But although I see the following message in the Log:
ProcessInformationTask timed out
I also see this line in my log
Adding the process information took 10001 ms
I wonder why this can happen because I want to cancel the Task after 5 seconds (if not finished). So I expect the task to last 5 seconds max. How can I solve this? Probably the cancelation isn't setup properly?
Code where I call my task
string additionalInformation = null;
var contextInfo = new StringBuilder();
var xmlWriterSettings = new XmlWriterSettings()
{
OmitXmlDeclaration = true,
ConformanceLevel = ConformanceLevel.Fragment
};
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
using (XmlWriter xmlWriter = XmlWriter.Create(contextInfo, xmlWriterSettings))
{
try
{
xmlWriter.WriteStartElement("AdditionalInformation");
//Write xml (not long running)
var watch = System.Diagnostics.Stopwatch.StartNew();
string processInformation = AddProcessesInformation(xmlWriterSettings);
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
Log.Info("Adding the process information took : " + elapsedMs + " ms");
if (!string.IsNullOrEmpty(processInformation))
{
xmlWriter.WriteRaw(processInformation);
}
//Write xml (not long running)
xmlWriter.WriteEndElement();
additionalInformation = contextInfo.ToString();
}
catch (Exception e)
{
Log.Info("An exception occured during writing the additional information: " + e.Message);
return false;
}
return true;
}
Task method
private static string AddProcessesInformation(XmlWriterSettings xmlWriterSettings)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var contextInfo = new StringBuilder();
var processInformationTask = Task<string>.Factory.StartNew(() =>
{
if (token.IsCancellationRequested)
{
Log.Info("Cancellation request for the ProcessInformationTask");
}
Thread.Sleep(10000);
return "Ran without problems'";
}, token);
if (!processInformationTask.Wait(5000, token))
{
Log.Info("ProcessInformationTask timed out");
tokenSource.Cancel();
}
return processInformationTask?.Result;
}
I think you should check if cancellation is requested multiple Times, after each step in your method. Here is example using for loop:
private static string AddProcessesInformation(XmlWriterSettings xmlWriterSettings)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var contextInfo = new StringBuilder();
var processInformationTask = Task<string>.Factory.StartNew(() =>
{
for(int i = 0; i < 10; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancellation request for the ProcessInformationTask");
return string.Empty;
}
Thread.Sleep(1000);
}
return "Ran without problems'";
}, token);
if (!processInformationTask.Wait(5000, token))
{
Console.WriteLine("ProcessInformationTask timed out");
tokenSource.Cancel();
}
return processInformationTask?.Result;
}
If inside task method you call other methods, you can pass cancellation token to them and use method token.ThrowIfCancellationRequested() inside.
var processInformationTask = Task<string>.Factory.StartNew(() =>
{
try
{
Method1(token);
Thread.Sleep(1000);
Method2(token);
Thread.Sleep(1000);
}
catch(OperationCanceledException ex)
{
return string.Empty;
}
return "Ran without problems'";
}, token);
private static void Method1(CancellationToken token)
{
token.ThrowIfCancellationRequested();
// do more work
}