I'm currently working on a Discord bot written in C#. It is supposed to celebrate the birthday of the users in a server, once they have provided their day and month of birth by sending a message (user-birthday pairs are stored inside a local .txt file). A birthday is celebrated by sending an embed in the main channel of the guild (with which it shares the ID parameter) if the date of execution happens to be someone's birthday.
The content of the Ready event handler of the DiscordSocketClient element within the Program class and a partial call stack are shown below:
public DiscordSocketClient Client;
//...
private async Task Client_Ready()
{
await Commands.CelebrateUsers(Client);
}
also
public static async Task CelebrateUsers(DiscordSocketClient client)
{
DateTime now = DateTime.Now;
List<ulong> users = new List<ulong>();
using (StreamReader reader = new StreamReader(#"docs\birthdays.txt"))
{
string date;
ulong user;
while ((date = reader.ReadLine()) != null)
{
user = Convert.ToUInt64(reader.ReadLine());
int[] dm = date.Split(' ').Select(x=>Convert.ToInt32(x)).ToArray();
if(now.Day == dm[0] && now.Month == dm[1])
{
foreach(SocketGuild g in client.Guilds)
{
if(g.Users.FirstOrDefault(x=>x.Id == user) != null)
await CelebrateUser(user, g);
}
}
}
}
}
private static async Task CelebrateUser(ulong id, SocketGuild guild)
{
var embed = new EmbedBuilder()
{
Title = "Auguri ",
Color = Discord.Color.Green,
ThumbnailUrl = "https://c7.uihere.com/files/240/249/186/hot-air-balloon-birthday-balloon.jpg",
ImageUrl = guild.GetUser(id).GetAvatarUrl(size:900),
Footer =
{
IconUrl = "https://cdn.discordapp.com/avatars/406195350903193600/9629b569007720204f7fe775a9be0aeb.png",
Text = "Generato da DrKikkoCeccato++."
},
Timestamp = DateTimeOffset.Now
};
await guild.DefaultChannel.SendMessageAsync("",embed:embed.Build());
}
However, I have an issue with the last function. The last piece of code (the "SendMessageAsync" part) is not even called.
Upon the initialization of the embed, the Log event of the DiscordSocketClient is fired with the following records:
"A Ready handler is blocking the gateway task."
"A Ready handler has thrown an unhandled exception."
The state changes to "Ready" immediately afterwards. Needless to say, my embed is not being sent.
I don't get what the problem might be, since every action is marked as "asynchronous" (in order to avoid problems with the 3-second timeout of the "Ready" event). Could anyone help me?
Thanks in advance.
Related
I have a c# WebApi project in which the users can make orders in a website, each order after payment complete will execute a function called ConfirmOrder which will update the order status from STB to COMPLETED.
In the following function that looks like this:
public static void ConfirmOrder(string piva, string orderID, double importo = 0, string transazione = "", string paymentID = "", string tipo = "MENU")
{
string connectionString = getConnectionString(piva);
using var connection = new MySqlConnection(connectionString);
string query_menu = "QUERY";
string query_pagamenti = "QUERY";
using var cmd = new MySqlCommand(query_pagamenti, connection);
connection.Open();
cmd.Parameters.AddWithValue("#tipo", tipo.ToUpper());
cmd.Parameters.AddWithValue("#importo", importo);
cmd.Parameters.AddWithValue("#transazione", transazione);
cmd.Parameters.AddWithValue("#dataOra", DateTime.Now);
cmd.Parameters.AddWithValue("#orderID", orderID);
cmd.Parameters.AddWithValue("#paymentID", paymentID);
cmd.Prepare();
cmd.ExecuteNonQuery();
cmd.CommandText = query_menu;
cmd.ExecuteNonQuery();
if (!tipo.Equals("MENU"))
{
EmailHelper.SendRiepilogo(piva, int.Parse(orderID)); // SENDING SUMMARY MAIL
}
}
I'm calling another function SendRiepilogo which sends a summary to the user and the shop, but in this case i can't wait for that function response but it have to be executed for it's own without stucking ConfirmOrder callback.. so i can't wait for SendRiepilogo to be executed, at this point i've read about IHostingService, but i can't figure out on how i could migrate my SendRiepilogo to a IHostingService and run it from ConfirmOrder...
My SendRiepilogo looks like this:
public static async void SendRiepilogo(string piva, int idOrdine)
{
var order = GetOrdine(piva, idOrdine);
if (order == null)
{
return;
}
try
{
var negozio = getNegozio(order.idNegozio);
var from = new MailAddress("ordini#visualorder.it", "VisualOrder");
var to = new MailAddress(order.cliente.FirstOrDefault().email);
using MemoryStream ms = new MemoryStream();
QRCodeGenerator qrGenerator = new QRCodeGenerator();
QRCodeData qrCodeData = qrGenerator.CreateQrCode("vo/" + idOrdine, QRCodeGenerator.ECCLevel.Q);
Base64QRCode qrCode = new Base64QRCode(qrCodeData);
byte[] byteQr = Convert.FromBase64String(qrCode.GetGraphic(20));
MemoryStream streamQr = new MemoryStream(byteQr);
var qrImage = new LinkedResource(streamQr, MediaTypeNames.Image.Jpeg)
{
ContentId = "qrImage"
};
string nome = order.cliente.FirstOrDefault().nome;
var orderEmail = new { idOrdine, order, nome, negozio };
byte[] byteLogo = Convert.FromBase64String(System.Text.Encoding.UTF8.GetString(negozio.logo));
MemoryStream streamLogo = new MemoryStream(byteLogo);
var logoImage = new LinkedResource(streamLogo, MediaTypeNames.Image.Jpeg)
{
ContentId = "logoImage"
};
string template = File.ReadAllText("Views/Emails/EmailRiepilogo.cshtml");
var htmlBody = Engine.Razor.RunCompile(template, "riepilogo", null, orderEmail);
AlternateView alternateView = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);
alternateView.LinkedResources.Add(qrImage);
alternateView.LinkedResources.Add(logoImage);
var message = new MailMessage(from, to)
{
Subject = "Riepilogo ordine",
Body = htmlBody
};
message.IsBodyHtml = true;
message.AlternateViews.Add(alternateView);
using var smtp = new SmtpClient("smtps.aruba.it", 587)
{
EnableSsl = true,
Credentials = new NetworkCredential("XXX", "XXX")
};
await smtp.SendMailAsync(message); // sending email to user
await smtp.SendMailAsync(MessageNegozio(order, idOrdine, negozio)); // sending email to shop
}
catch (Exception e)
{
return;
}
ConfirmEmail(piva, idOrdine); // setting "EMAIL SENT" flag in DB to true
return;
}
A background (hosted) service is a completely different service, using its own thread to do its job. You can't have your controller "run" something on that service, you have to tell it what to do, and have it do it.
The Background tasks with hosted services section in the docs shows two different ways a long running background service can work :
A timed service can run each time a timer fires and do a periodic job, as long as the application is running
A queued service waits for messages in a queue and performs a job when a message arrives
Sending an email fits into the second case. You could use the documentation example almost as-is. You can create an IBackgroundTaskQueue interface that clients like your controller can use to submit jobs to run in the background:
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
This interface can be added as a dependency in your container's constructor.
Assuming the injected service is called myJobQueue, the controller can enqueue a job to run in the background with :
IBackgroundTaskQueue _myJobQueue
public MyController(IBackgroundTaskQueue myJobQueue)
{
_myJobQueue=myJobQueue;
}
public void ConfirmOrder(...)
{
...
if (!tipo.Equals("MENU"))
{
var ordId=int.Parse(orderID);
_myJobQueue.QueueBackgroundWorkItem(ct=>EmailHelper.SendRiepilogoAsync(piva,ordId ));
}
async void should only be used for asynchronous event handlers. That's not what SendRiepilogo is. async void methods can't be awaited, they are essentially fire-and-forget methods that may never run, as the application doesn't know it has to await them. The correct syntax should be :
public static async Task SendRiepilogoAsync(string piva, int idOrdine)
{
...
}
The rest of the documentation example can be used as-is.
Simplifying the service
Instead of a generic queued service that runs any available job, you could create a queue that accepts specific message classes only, only an address and order ID, and have the service do the job of retrieving any data and sending the email. Essentially, SendRiepilogoAsync becomes part of the background service. This allows creating services that could eg batch emails, send several emails concurrently, apply throttling etc.
This would allow reusing expensive resources or perform expensive operations just once, eg create the SmptClient and authenticate before starting to process queue messages
I need to call Web API(Hosted on different network) from windows application from users machine for 30,000 times within one hour of time span.
I tried Multithreading to achieve the same but it is not working(giving system out of memory exceprion).
I used TreadPool as below
private static object threadLock = new object();
public delegate void BarDelegate();
int ThreadCount = dtExcel.Rows.Count;
private void button2_Click(object sender, EventArgs e)
{
for (int i = 0; i < ThreadCount - 1; i++)
{
ThreadPool.QueueUserWorkItem(output => CallAPI());
}
}
public void CallAPI()
{
string branchCode = "",
para1 = dtExcel.Rows[progressBar.Value]["para1"].ToString(),
para2 = "324",
para3 = "Test",
para4 = dtExcel.Rows[progressBar.Value]["para4"].ToString();
//Console.WriteLine(Thread.CurrentThread.Name + ": " + progressBar.Value);
var service = new APIService();
var resp = service.CallAPIService(para1, para2, para3, para4, para5);
if (resp.IsSuccess == true)
{
DataGridViewRow dtGrdVwR = dataGrid.Rows[progressBar.Value];
dtGrdVwR.Cells[3].Value = "Success";
}
else
{
DataGridViewRow dtGrdVwR = dataGrid.Rows[progressBar.Value];
dtGrdVwR.Cells[3].Value = "Failed: "+ resp.Message;
}
try
{
this.Invoke(new BarDelegate(UpdateBar));
}
catch
{
}
}
private void UpdateBar()
{
lblEndTime.Text = DateTime.Now.ToString();
progressBar.Value++;
if (progressBar.Value == progressBar.Maximum)
{
// We are finished and the progress bar is full.
}
}
Here dtExcel has 30,000 records(Uploaded by user from excel) which needs to processed within one hour and update the status of executed record in respective row in dataGrid.
The API call is made over network where single call takes approximate 1 to 2 seconds to execute.
service.CallAPIService(para1, para2, para3, para4, para5);
The above method internally performs heavy task like request encryption and digital signature and response decryption and digital signature verification.
Please help me with the best way possible where i can perform the task within time period and without getting SystemOutOfmemoryException.
Thanks in Advance.
Right now your code is horribly broken because of the race condition accessing progressBar.Value. It's pointless to discuss any other issues, because you are going to totally reorganize your code to fix the race condition, rendering other comments obsolete.
Fix it so that you don't have N threads all trying to process item #1, and then ask a new question with your new code.
I am developing a chatbot using the Microsoft bot framework in C#. We have a functionality where it queries the database and returns the result, but it might take up to 25-30 secs for the result to return.
By that time bot says "cannot send,please retry". Is there a way to increase this timeout? Or can we have something like "please wait" message for the user so that user will know that the request is processing?
It's hard coded in SDK, we're not able to override the message like "Couldn't send, retry". As Nicolas said, a workaround is to send a proactive message to user.
For example you can firstly create a ConversationStarter.cs class like this:
public class ConversationStarter
{
//Note: Of course you don't want these here. Eventually you will need to save these in some table
//Having them here as static variables means we can only remember one user :)
public static string fromId;
public static string fromName;
public static string toId;
public static string toName;
public static string serviceUrl;
public static string channelId;
public static string conversationId;
//This will send an adhoc message to the user
public static async Task Resume(string conversationId, string channelId)
{
var userAccount = new ChannelAccount(toId, toName);
var botAccount = new ChannelAccount(fromId, fromName);
var connector = new ConnectorClient(new Uri(serviceUrl));
IMessageActivity message = Activity.CreateMessageActivity();
if (!string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(channelId))
{
message.ChannelId = channelId;
}
else
{
conversationId = (await connector.Conversations.CreateDirectConversationAsync(botAccount, userAccount)).Id;
}
message.From = botAccount;
message.Recipient = userAccount;
message.Conversation = new ConversationAccount(id: conversationId);
message.Text = "Hello, work is done!";
message.Locale = "en-Us";
await connector.Conversations.SendToConversationAsync((Activity)message);
}
}
Then in your dialog, you can code like this:
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
//We need to keep this data so we know who to send the message to. Assume this would be stored somewhere, e.g. an Azure Table
ConversationStarter.toId = message.From.Id;
ConversationStarter.toName = message.From.Name;
ConversationStarter.fromId = message.Recipient.Id;
ConversationStarter.fromName = message.Recipient.Name;
ConversationStarter.serviceUrl = message.ServiceUrl;
ConversationStarter.channelId = message.ChannelId;
ConversationStarter.conversationId = message.Conversation.Id;
await context.PostAsync("Please wait, we're processing...");
Processing();
}
public async Task Processing()
{
//replace the task.delay() method with your task.
await Task.Delay(30000).ContinueWith((t) =>
{
ConversationStarter.Resume(ConversationStarter.conversationId, ConversationStarter.channelId);
});
}
Then Task.Delay(30000) method is used for a 30s task testing, you should be able to replace it with your task for retrieving data from your database.
You should do the following:
acknowledge the request of the user with a basic text reply
save the message information and process the request
make a proactive message once you got the reply of your system
I am working on an ASP.NET Webform project (legacy code).On my button_click event i am sending sms message to all the datas populated in this.
var customerSMS = BusinessLayer.SMS.SmsSetup.GetAllCustomerSMS(OfficeId);
This takes around 15seconds to do all the computing and get the data(1000rows)
from the Db.And for each data it runs through the loop and does validation and
sends the sms and it does take time.I want to do this task in background and
redirect the user to the index page and the background process continues till it
gets out of the loop.I am new to this and still learning this beautiful
language C#.I did go through this amazing Asynchronous Programming async/await
and Multithreading approach and got hold of it only in simple WindowsForm
applications.Any reference/code snippet/best approach with a simple explanation for my case would be helpful.
My button click event code :
protected void ReturntoDashboard_Click(object sender, EventArgs e)
{
sms = Everest.Net.BusinessLayer.SMS.SmsSetup.GetSmsSetUp(OfficeId);
if (sms.EnableSmsData && sms.SmsCount > 0)
{
#region Loan Section
var smsLoan = Everest.Net.BusinessLayer.SMS.SmsSetup.GetLoanId(s.Sms_AccountNumber);
var loanId =
BusinessLayer.SMS.SmsSetup.GetLoanIdValue(s.Sms_AccountNumber);
var dateexceeded =
BusinessLayer.SMS.SmsSetup.IsDateExceeded(loanId);
if (smsLoan != null && dateexceeded == true)
{
foreach (Common.SMS.SMSSetup sm in smsLoan)
{
var smsClosingBalanceLoan = BusinessLayer.SMS.SmsSetup.GetAmountForLoanAlert( sm.LoanId,
BusinessLayer.Core.DateConversion
.GetCurrentServerDate()
.AddDays(sms.DaysbeforeLoanalerts).ToString());
if (smsClosingBalanceLoan != null)
{
if (smsClosingBalanceLoan.LoanAmountToPay > 0)
{
int smsSentAlertCount = sms.LoanAlertCount;
var logCount = BusinessLayer.SMS.SmsSetup.GetLoanSmsAlertSentCount(DateTime.Now.AddDays(-smsSentAlertCount).ToString("yyyy-MM-dd"), DateTime.Now.ToString("yyyy-MM-dd"), sm.LoanAccountNumber);
if (logCount < smsSentAlertCount)
{
smsLog = new Everest.Net.Common.SMS.SMSSetup();
finalMessage = "Dear Member, Your Loan accnt " + sm.LoanAccountNumber + " with Principal"+ "+" + "Int Amnt: Rs." + smsClosingBalanceLoan.LoanAmountToPay + " need to be payed.Thank You," + officeName.OfficeName;
smsLog.LogServiceType = "Loan";
smsLog.LogSmsType = s.Sms_SmsType;
smsLog.LogSmsMessage = finalMessage;
smsLog.LogCustomerId = s.CustomerId.ToString();
smsLog.LogAccountNumber = s.Sms_AccountNumber;
smsLog.LogAccountType = s.Sms_AccountType;
smsLog.LogSmsSentDate = BusinessLayer.Core.DateConversion.GetCurrentServerDate();
smsLog.LogSmsFailedDate = "";
smsLog.LogSentStatus = true;
smsLog.LogUserId = UserId;
smsLog.LogSmsFailedMessage = "";
try
{
var result = Everest.Net.BusinessLayer.SMS.smsParameters.SendSMS(sms.FromNum, sms.Token, sms.Url, cellNum, finalMessage);
}
catch (Exception ex)
{
smsLog.LogSmsFailedDate = System.DateTime.Now.ToString("MM/dd/yyyy HHmmss");
smsLog.LogSentStatus = false;
smsLog.LogSmsFailedMessage = ex.Message;
Everest.Net.BusinessLayer.SMS.SmsSetup.InsertSMSLog(smsLog);
}
sms = Everest.Net.BusinessLayer.SMS.SmsSetup.GetSmsSetUp(OfficeId);
sms.SmsCount = sms.SmsCount - 1;
Everest.Net.BusinessLayer.SMS.SmsSetup.UpdateSmsSetup(sms);
Everest.Net.BusinessLayer.SMS.SmsSetup.InsertSMSLog(smsLog);
}
}
}
}
}
}
}
}
catch (Exception ex)
The ideal solution would remove the responsibility of sending the SMS from the web application itself. Instead, the web application should create a database record containing the message and recipient addresses, and a separate background job (e.g. a Windows Service) should poll the database and send SMS messages when neeeded. This is the best solution in terms of fault tolerance and auditability, because there is a permanent record of the messaging job which can be resumed if the system fails.
That being said, maybe you don't want to go to all that trouble. If you feel strongly that you wish to send the SMS directly from the ASP.NET application, you will need to create a Task and queue it to run using QueueBackgroundWorkitem. You will need to refactor your code a bit.
Move all the logic for sending the SMS into a separate function that accepts all the information needed as parameters. For example,
static void SendSMS(string[] addresses, string messagetext)
{
//Put your SMS code here
}
When you need to call the function, queue it as a background item
HostingEnvironment.QueueBackgroundWorkItem(a => SendSMS(addresses, messageText));
If your worker task needs to access its own cancellation token (e.g. if it is supposed to loop until cancelled), it is passed as an argument to the lambda expression. So you could modify the prototype
static void SendSMS(string[] addresses, string messagetext, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
//Put your code here
}
}
and pass it thus:
HostingEnvironment.QueueBackgroundWorkItem(token => SendSMS(addresses, messageText, token));
Placing the task in the background queue ensures that ASP.NET keeps track of the thread, doesn't try to garbage collect it, and shuts it down properly when the application pool needs to shut down.
After queuing the background operation, your page can render is content per usual and conclude the HTTP response while the task continues to execute.
Good afternoon all!
The goal of the project:
Build a notification program with client, console, and server executable. Only selected users should get the notification.
The problem:
Sometimes the code works great, and everything works (20% of runs). The rest of the time, it will mess up the order the data is being sent in.
The code:
Server (console = TCPclient):
private void Connect()
{
string username = ReadFromConsole();
if (IsUserAllowed(username)) // Receive username
SendToConsole(bool.TrueString); // Send confirmation
else
{
SendToConsole(bool.FalseString); // Send denial
console.Close();
return;
}
string messageID = ReadFromConsole(); // Receive MessageID
string recipientCount = ReadFromConsole();
int numOfRecipients = int.Parse(recipientCount); // Receive and parse number of recipients
List<string> recipients = new List<string>();
for (int i = 0; i < numOfRecipients; i++)
{
string recipient = ReadFromConsole();
recipients.Add(recipient); // Receive recipient, add to list (required for Message)
}
string department = ReadFromConsole(); // Receive department string
string visibleTime = ReadFromConsole(); // Receive visibility timespan
string expiration = ReadFromConsole(); // Receive expiration datetime
StoreRTF(messageID); // Receive and store RTF file
console.Close(); // Connection is done, close
Message message = new Message(messageID, department, recipients, visibleTime, expiration);
}
Console (server = TCPclient):
private void SendMessage()
{
SendToServer(Environment.UserName);
if (bool.Parse(ReadFromServer()))
{
// User is allowed, continue
string messageID = DateTime.Now.ToUniversalTime().Ticks.ToString();
SendToServer(messageID); // MessageID
string recipientCount = lvRecipients.Items.Count.ToString();
SendToServer(lvRecipients.Items.Count.ToString()); // Amount of recipients
foreach (string item in lvRecipients.Items) // Loop to send each recipient
{
SendToServer(item);
}
string department = TB_Department.Text;
SendToServer(department); // Send department string
string visibleTime = TimeSpan.FromSeconds(SLIDER_VisibleTime.Value).Ticks.ToString();
SendToServer(visibleTime); // Send message visibility time
string expiration = DateTime.Now.ToUniversalTime().AddMinutes(2).ToString();
SendToServer(expiration); //TODO add UI control for this
SendRTFToServer(); // Send RTF file
MessageBox.Show(
"Your designated MessageID is: " + messageID + Environment.NewLine +
"Message upload is succesful.",
"Complete",
MessageBoxButton.OK);
}
else
{
// User is not allowed. Report to user. Disconnect (will be managed by the finally block)
MessageBox.Show("You are not allowed to upload messages to the server.", "Access denied", MessageBoxButton.OK, MessageBoxImage.Stop);
return;
}
}
Send and receive parts (same between console/server/client):
private void SendToServer(string toSend)
{
while (server.GetStream().DataAvailable)
{
// Should wait
}
StreamWriter writer = new StreamWriter(server.GetStream());
writer.WriteLine(toSend);
writer.Flush();
}
private void SendRTFToServer()
{
while (server.GetStream().DataAvailable)
{
// Should wait
}
File.Open(RTFLocation, FileMode.Open, FileAccess.Read).CopyTo(server.GetStream());
server.GetStream().Flush();
server.GetStream().Close();
}
private string ReadFromServer()
{
server.GetStream().Flush();
StreamReader reader = new StreamReader(server.GetStream());
return reader.ReadLine();
}
I have also tried different loops, implementations, switching to byte[]...
After lots of debugging I am getting nowhere. I have checked which information is leaving the console, and that all checks out and is in the right order. However, at the server end it seems to be receiving it in an entirely different order.
Anyone have an idea what is causing this?
I've found how to properly handle this. Code for the relevant parts can be found here:
C# Sockets send/receive problems and questions
Hope this may help someone in the future!