Threads staying alive after execution - c#

I have created a ThreadManager class that handles Threads and its task is to add new threads and clean up the dead threads. However, the threads that are created remain alive and in ThreadState.WaitSleepJoin state. I have checked that the body has successfully finished execution. Any ideas?
public bool TryAddThread(ThreadStart threadBody, ThreadStartInfo startInfo)
{
bool success = false;
// Validate arguments
if (threadBody == null || startInfo == null)
{
return false;
}
if (!Monitor.TryEnter(_lock) || !_allowNewThreads)
{
return false;
}
try
{
Thread newThread = new Thread(threadBody);
StartThread(newThread, null, startInfo);
success = true;
}
finally
{
Monitor.Exit(_lock);
}
return success;
}
private void StartThread(Thread newThread, object threadParams, ThreadStartInfo startInfo)
{
if (newThread == null || startInfo == null)
{
return;
}
// Apply start info
newThread.Name = startInfo.Name;
newThread.SetApartmentState(startInfo.ApartmentState);
newThread.IsBackground = startInfo.IsBackground;
if (threadParams == null)
{
newThread.Start();
}
else
{
newThread.Start(threadParams);
}
_threads.Add(newThread);
RemoveDeadThreads();
}
public void RemoveDeadThreads()
{
_threads.RemoveAll(t => (!t.IsAlive));
}
Execution in main thread:
public void InsertAsync(AP p, APr pr)
{
ParameterizedThreadStart thread = new ParameterizedThreadStart(InsertPr);
List<object> parameters = new List<object>();
// Create new controller. Must be before the thread to avoid cross-thread operation exception.
PageController controller = new PageController();
controller.Initialize(siteWebBrowser);
parameters.Add(controller);
parameters.Add(p);
parameters.Add(pr);
parameters.Add(_session);
// If the thread cannot start notify listeners
if (!_threadManager.TryAddThread(thread, parameters, new ThreadStartInfo("InsertAsync", ApartmentState.STA, true)) && ThreadCreationFailed != null)
{
_logger.Error("InsertAsync: Could not start thread.");
ThreadCreationFailed();
}
}
private static void InsertPr(object o)
{
try
{
_logger.Debug("Thread start - InsertPr");
List<object> parameters = (List<object>)o;
PageController controller = (PageController)parameters[0];
AP p = (AP)parameters[1];
APr pr = (APr)parameters[2];
Session session = (Session)parameters[3];
if (patient == null)
{
throw new ArgumentException("Null patient.");
}
session.WaitForHistorySynchronizationSuspension();
if (Program.ShouldAbortBackgroundOperations)
{
throw new Exception("Aborting..");
}
session.DoingSomeJob = true;
controller.ClearCurrent();
controller.GoToHomePage(3, true);
controller.ClickNew();
controller.SearchForP(p.Id);
try
{
controller.WaitUntilDivExists(Constants.NewPrContainerDivId);
}
catch (Exception)
{
_logger.Error("InsertAsync: Error while waiting for div '" + Constants.NewPrContainerDivId + "' to appear.");
throw;
}
if (PrInsertionCompleted != null)
{
PrInsertionCompleted();
}
}
catch (Exception ex)
{
_logger.ErrorException("InsertAsync", ex);
if (InsertionFailed != null)
{
InsertionFailed(Constants.MessageFailed);
}
}
}

You can ask the CLR to automatically abort threads for you when the main startup thread of the program terminates. But that's not automatic, you have to explicitly set the thread's IsBackground property to true. Threadpool threads have that property turned on automatically.

WaitSleepJoin means the thread has blocked itself with a call to lock (Monitor.Enter), a call to Thread.Sleep, or a call to Thread.Join, or some other thread synchronization object.
Maybe if you provide example thread entry point that is causing this thread state, someone can provide a more detailed answer.

Related

Async function freezes UI thread

I have an async function which still freezes / lags the UI thread for me when I execute it. This is my function calling it.
private void TcpListenerLogic(object sender, string e)
{
Application.Current.Dispatcher.BeginInvoke((Action)async delegate {
try
{
dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
if (results.test_id != null)
{
// Get properties for new anchor
string testInformation = await CommunicationCommands.getJsonFromURL(
"http://" + ServerIP + ":" + ServerPort + "/api/" + results.test_id);
}
}
catch (Exception exception)
{
// Writing some Trace.WriteLine()'s
}
});
}
And this is the async function that freezes my UI Thread
public static async Task<string> getJsonFromURL(string url)
{
try
{
string returnString = null;
using (System.Net.WebClient client = new System.Net.WebClient())
{
returnString = await client.DownloadStringTaskAsync(url);
}
return returnString;
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return null;
}
}
I already tried to make everything in TcpListenerLogic run in a new Thread:
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
}).Start();
Which resulted in the whole UI completely freezing. And I tried to make TcpListenerLogic async and await the dispatcher, which also made everything freeze permanently. I also tried to make TcpListenerLogic async and leave the dispatcher. The dispatcher is only there because I normally have some UI code in there, which I left out for my tests.
I have ventured far through the internet, but no BackgroundWorker, ThreadPool or other methods helped me in my endeavour.
If anyone has help for this particular problem, or a resource that would improve my understanding of async functions in C#, I would much appreciate it.
Edit
As requested a deeper insight in how this event handler is called.
I have System.Net.Websocket, which is connected to the Backend API I am working with and triggers an event, everytime he receives new Data. To guarantee the socket listens as longs as it is open, there is a while loop which checks for the client state:
public event EventHandler<string> TcpReceived;
public async void StartListener(string ip, int port, string path)
{
try
{
using (client = new ClientWebSocket())
{
try
{ // Connect to backend
Uri serverUri = new Uri("ws://" + ip + ":" + port.ToString() + path );
await client.ConnectAsync(serverUri, CancellationToken.None);
}
catch (Exception ex)
{
BackendSettings.IsConnected = false;
Debug.WriteLine("Error connecting TCP Socket: " + ex.ToString());
}
state = client.State;
// Grab packages send in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
{
try
{
// **Just formatting the received data until here and writing it into the "message" variable**//
TcpReceived(this, message);
// Close connection on command
if (result.MessageType == WebSocketMessageType.Close)
{
Debug.WriteLine("Closing TCP Socket.");
shouldstayclosed = true;
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
state = client.State;
}
catch
{
BackendSettings.IsConnected = false;
state = client.State;
}
}
state = client.State;
}
}
catch (Exception ex)
{
// Some error messages and settings handling
}
}
The Event has a handler attached:
TcpReceived += TcpListener_TcpReceived;
And this is the Handler, which calls the previously seen "TcpListenereLogic".
private void TcpListener_TcpReceived(object sender, string e)
{
TcpListenerLogic(sender, e);
//App.Current.Dispatcher.BeginInvoke(new Action(() => {
// TcpListenerLogic(sender, e);
//}));
//new Thread(() =>
//{
// Thread.CurrentThread.IsBackground = true;
// TcpListenerLogic(sender, e);
//}).Start();
}
I previously had the "TcpListenereLogic" as the handler, but I wanted to try different methods to call it. I also left in the commented out part, to show how the call of "TcpListenereLogic" looked already. All my attempts were with all mentioned setups and sadly lead to nothing.
Thank you very much #TheodorZoulias for helping me to find the solution to my problem.
It turns out it wasn't the async function itself, but rather how often it gets called. It got called roughly ~120 times every second.
My solution starts by calling the Listener method over a new Thread:
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
MainWindow.tcpListener.StartListener(ip, portNumber, "/api/");
}).Start();
To limit the amount of calls that happen every second I added a dispatcher timer, that resets a bool after it has been used for a call, by my Event.
readonly System.Windows.Threading.DispatcherTimer packageIntervallTimer =
new System.Windows.Threading.DispatcherTimer();
bool readyForNewPackage = true;
private void ReadyForPackage(object sender, EventArgs e)
{
readyForNewPackage = true;
}
public async void StartListener(string ip, int port, string path)
{
packageIntervallTimer.Interval = TimeSpan.FromMilliseconds(50);
packageIntervallTimer.Tick += (s, e) => { Task.Run(() => ReadyForPackage(s, e)); };
packageIntervallTimer.Start();
Then I wrapped everything inside the while loop into an if condition based on the bool, the most important part was to have my "event EventHandler TcpReceived" in there:
// Grab packages sent in backend
while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseSent)
{
if (readyForNewPackage == true)
{
readyForNewPackage = false;
try
{
....
TcpReceived(this, message);
....
}
catch
{
...
}
}
}
I added my TcpListenerLogic to the Eventhandler:
TcpReceived += TcpListenerLogic;
And my TcpListenerLogic now looked like this (names have been changed):
private async void TcpListenerLogic(object sender, string e)
{
try
{
dynamic results = JsonConvert.DeserializeObject<dynamic>(e);
if (results.test_id != null)
{
string testID = "";
if (results.test_id is JValue jValueTestId)
{
testID = jValueTestId.Value.ToString();
}
else if (results.test_id is string)
{
testID = results.test_id;
}
// Get properties for new object
string information = await CommunicationCommands.getJsonFromURL(
"http://" + ServerIP + ":" + ServerPort + "/api/" + testID );
if (information != null)
{
await App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
// Create object out of the json string
TestStatus testStatus = new TestStatus();
testStatus.Deserialize(information);
if (CommunicationCommands.isNameAlreadyInCollection(testStatus.name) == false)
{
// Add new object to the list
CommunicationCommands.allFoundTests.Add(testStatus);
}
}));
{
}
catch (Exception exception)
{
....
}
}
Adding a new Thread to execute any step results in problems, so keep in mind that all this uses the thread created at the beginning for "StartListener"

How would I create dynamic tasks/threads to trigger the same function/method in which the tasks/threads are controlled from a Database?

I build a service that would call tasks to be performed. This is table driven and dynamic. So at each tick of the service (every 10 seconds) it would call the SQL table and find out what is active. If it is active, it will build a new task and put it into an object list. Each task calls the same function but I pass through a unique datarow (which contains a unique ID from the datatable) into the passing parameters for the method.
When the tasks run at the same time, I receive random errors (from the database calls) that have no relation to the task that is supposed to be running.
So the question is, is it possible that the tasks that run the same method/function at the same time will step on each other and causing the threads to intersect?
Here is my code:
namespace ReportService
{
public partial class Service1 : ServiceBase
{
//public timer
private static System.Timers.Timer timerReports = null;
//collection of tasks
//private static BlockingCollection<ReportTasks> tasksCollection = new BlockingCollection<ReportTasks>();
private static List<ReportTasks> tasksCollection = new List<ReportTasks>();
#region Service functions
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
try
{
ExceptionInfo exceptioninfo = new ExceptionInfo();
exceptioninfo.LogType = "L";
exceptioninfo.ClassName = MethodBase.GetCurrentMethod().DeclaringType.Name.ToString();
exceptioninfo.MethodName = MethodBase.GetCurrentMethod().Name;
exceptioninfo.OptionalMessage = "Service On";
Log.WriteToLog(exceptioninfo, ConfigSettings.GetServiceSettings());
timerReports = new System.Timers.Timer();
timerReports.Interval = 30000; //30 secconds
timerReports.Elapsed += new ElapsedEventHandler(this.timerReports_Tick);
timerReports.Enabled = true;
}
catch (Exception ex)
{
//Get Service settings
dynamic ServiceSettings = ConfigSettings.GetServiceSettings();
if (ex.Data["WrittenToLog"] == null)
{
//set new object
ex.Data.Add("WrittenToLog", true);
//build exception object
ExceptionInfo exceptioninfo = new ExceptionInfo();
exceptioninfo.LogType = "E";
exceptioninfo.ClassName = MethodBase.GetCurrentMethod().DeclaringType.Name.ToString();
exceptioninfo.MethodName = MethodBase.GetCurrentMethod().Name;
exceptioninfo.InnerException = ex;
//write to log
Log.WriteToLog(exceptioninfo, ServiceSettings);
}
else if ((bool)ex.Data["WrittenToLog"] == false)
{
//set written to log as true
ex.Data["WrittenToLog"] = true;
//build exception object
ExceptionInfo exceptioninfo = new ExceptionInfo();
exceptioninfo.LogType = "E";
exceptioninfo.ClassName = MethodBase.GetCurrentMethod().DeclaringType.Name.ToString();
exceptioninfo.MethodName = MethodBase.GetCurrentMethod().Name;
exceptioninfo.InnerException = ex;
//write to log
Log.WriteToLog(exceptioninfo, ServiceSettings);
}
//throw exception to back out of process
//throw ex;
}
}
protected override void OnStop()
{
try
{
timerReports.Enabled = false;
ExceptionInfo exceptioninfo = new ExceptionInfo();
exceptioninfo.LogType = "L";
exceptioninfo.ClassName = MethodBase.GetCurrentMethod().DeclaringType.Name.ToString();
exceptioninfo.MethodName = MethodBase.GetCurrentMethod().Name;
exceptioninfo.OptionalMessage = "Service Off";
Log.WriteToLog(exceptioninfo, ConfigSettings.GetServiceSettings());
}
catch (Exception ex)
{
//Get Service settings
dynamic ServiceSettings = ConfigSettings.GetServiceSettings();
if (ex.Data["WrittenToLog"] == null)
{
//set new object
ex.Data.Add("WrittenToLog", true);
//build exception object
ExceptionInfo exceptioninfo = new ExceptionInfo();
exceptioninfo.LogType = "E";
exceptioninfo.ClassName = MethodBase.GetCurrentMethod().DeclaringType.Name.ToString();
exceptioninfo.MethodName = MethodBase.GetCurrentMethod().Name;
exceptioninfo.InnerException = ex;
//write to log
Log.WriteToLog(exceptioninfo, ServiceSettings);
}
else if ((bool)ex.Data["WrittenToLog"] == false)
{
//set written to log as true
ex.Data["WrittenToLog"] = true;
//build exception object
ExceptionInfo exceptioninfo = new ExceptionInfo();
exceptioninfo.LogType = "E";
exceptioninfo.ClassName = MethodBase.GetCurrentMethod().DeclaringType.Name.ToString();
exceptioninfo.MethodName = MethodBase.GetCurrentMethod().Name;
exceptioninfo.InnerException = ex;
//write to log
Log.WriteToLog(exceptioninfo, ServiceSettings);
}
//throw exception to back out of process
//throw ex;
}
}
#endregion
private void timerReports_Tick(object sender, ElapsedEventArgs e)
{
//check to see if task is running, if not, process EDI
try
{
//remove completed tasks
tasksCollection.RemoveAll(item => item.ReportTask.Status == TaskStatus.RanToCompletion);
//Get Service settings
dynamic ServiceSettings = ConfigSettings.GetServiceSettings();
//set the SQL command and parameters
SQLcommand Sqlcommandobj = new SQLcommand();
Sqlcommandobj.SQLcmd = #"SELECT *,'TABLE' AS [TABLE_NAME]
FROM EX_TABLE";
Sqlcommandobj.SQLcmdType = CommandType.Text;
//fill in list
DataSet dsReportSchedules = Queries.ServiceSQLExecute(ServiceSettings, Sqlcommandobj);
//loop through each schedule to add/remove tasks
foreach (DataRow drReport in dsReportSchedules.Tables["SCHEDULES"].Rows)
{
if (!tasksCollection.Any(item => item.ReportID == Helper.GetValueFromDataRowInt32(drReport, "REPORTS_SCHEDULE_ID")))
{
if (Helper.GetValueFromDataRowString(drReport, "ACTIVE") == "1" && Helper.GetValueFromDataRowString(drReport, "DELETE_DATE") == string.Empty)
{
//create cancellation for task
var ts = new CancellationTokenSource();
//create new task
Task newTask = new Task(() => ReportProcess.BeginProcessingReport(drReport, ServiceSettings), ts.Token);
//fill in report tasks object
ReportTasks ReportTasks = new ReportTasks();
ReportTasks.ReportID = Helper.GetValueFromDataRowInt32(drReport, "REPORTS_SCHEDULE_ID");
ReportTasks.ReportName = Helper.GetValueFromDataRowString(drReport, "NAME");
ReportTasks.ReportTask = newTask;
ReportTasks.TaskID = newTask.Id;
ReportTasks.Active = Convert.ToBoolean(drReport["ACTIVE"]);
ReportTasks.CancelTokenSource = ts;
//add to task collection
tasksCollection.Add(ReportTasks);
}
}
else
{
//remove if not active or deleted
if (Helper.GetValueFromDataRowString(drReport, "ACTIVE") != "1" || Helper.GetValueFromDataRowString(drReport, "DELETE_DATE") != string.Empty)
{
var itemToRemove = tasksCollection.SingleOrDefault(item => item.ReportID == Helper.GetValueFromDataRowInt32(drReport, "REPORTS_SCHEDULE_ID"));
if (itemToRemove.ReportID > 0)
{
//check to see if task is running
if (itemToRemove.ReportTask.Status == TaskStatus.Running)
{
itemToRemove.CancelTokenSource.Cancel();
}
//remove task from collection
tasksCollection.Remove(itemToRemove);
}
}
}
}
//trigger each task
foreach (var str in tasksCollection)
{
Console.WriteLine("Task: " + str.ReportName + " - Status: " + str.ReportTask.Status);
if (str.ReportTask.Status == TaskStatus.RanToCompletion | str.ReportTask.Status == TaskStatus.Created)
{
str.ReportTask.Start();
}
}
}
catch (Exception ex)
{
//Get Service settings
dynamic ServiceSettings = ConfigSettings.GetServiceSettings();
if (ex.Data["WrittenToLog"] == null)
{
//set new object
ex.Data.Add("WrittenToLog", true);
//build exception object
ExceptionInfo exceptioninfo = new ExceptionInfo();
exceptioninfo.LogType = "E";
exceptioninfo.ClassName = MethodBase.GetCurrentMethod().DeclaringType.Name.ToString();
exceptioninfo.MethodName = MethodBase.GetCurrentMethod().Name;
exceptioninfo.InnerException = ex;
//write to log
Log.WriteToLog(exceptioninfo, ServiceSettings);
}
else if ((bool)ex.Data["WrittenToLog"] == false)
{
//set written to log as true
ex.Data["WrittenToLog"] = true;
//build exception object
ExceptionInfo exceptioninfo = new ExceptionInfo();
exceptioninfo.LogType = "E";
exceptioninfo.ClassName = MethodBase.GetCurrentMethod().DeclaringType.Name.ToString();
exceptioninfo.MethodName = MethodBase.GetCurrentMethod().Name;
exceptioninfo.InnerException = ex;
//write to log
Log.WriteToLog(exceptioninfo, ServiceSettings);
}
//throw exception to back out of process
//throw ex;
}
}
}
}
In the above, during the timer tick, I go through each record in the task list and delete out the completed tasks. Then I grab the records from the DB and determine if they need to be added. It will also check to see if a user marked them for deleted or inactive and it will use a cancellation token to stop it and then remove it from the list. Last, it will loop through and trigger the tasks in the list.
This is the process that gets called on each task:
public static void BeginProcessingReport(Object drReportSchedule, dynamic
ServiceSettings)
{
}
and here is the object that contains the tasks for the List<>
public struct ReportTasks
{
public string ReportName;
public Int32 ReportID;
public int TaskID;
public Task ReportTask;
public bool Active;
public CancellationTokenSource CancelTokenSource;
public ReportTasks(string name, Int32 reportID, int id, Task task, DataRow drReport, bool active, CancellationTokenSource canceltokensource, CancellationToken canceltoken)
{
ReportName = name;
ReportID = reportID;
TaskID = id;
ReportTask = task;
Active = active;
CancelTokenSource = canceltokensource;
}
}
Hopefully that is enough information and any help will be greatly appreciated
UPDATE: I was able to resolve the issues by creating a thread for the main processing of the tasks. Moved the functionality out of the tick method and put it in its own method to be called by the thread.
So every tick from the timer will check the thread to see if its running or not.
private static Thread mainThread = null;
private void timerReports_Tick(object sender, ElapsedEventArgs e)
{
//create thread if null
if (mainThread == null)
{
mainThread = new Thread(new ThreadStart(Process));
}
//start thread if stopped or unstarted, else, it is still running and do nothing
if(mainThread.ThreadState == System.Threading.ThreadState.Stopped || mainThread.ThreadState == System.Threading.ThreadState.Unstarted)
{
mainThread.Start();
}
}
UPDATE:
The above code didnt work as I still had issues with tasks retriggering when they werent completed.
So let me reword my question:
How would I create dynamic tasks/threads to trigger the same function/method in which the tasks/threads are controlled from a Database? It would need the ability to remove threads and tasks if the records are deleted or set to Active = 0 which is a bit field in SQL to determine if the record should trigger or not.
By "step on each other" do you mean that the tick event occurs again whilst the previous event is still being processed?
In which case just disable the timer in the beginning of the tick event and re-enable when processing is complete

Returning value by a thread

Having this :
public Item_DataColl invoke_command_READ(string UCPTName)
{
networkingThread = new Thread(new ParameterizedThreadStart(thread_command_READ));
networkingThread.Start(new readingThreadParameter(UCPTName));
}
private Item_DataColl thread_command_READ_result(object parameter)
{
readingThreadParameter p = parameter as readingThreadParameter;
Item item;
if (p.UCPT_Name != null)
{
Item_DataColl resultSet;
try
{
OnProgressBarUpdate(progressBar.UnknownEnd);
resultSet = connector.command_READ(p.UCPT_Name);
readOperationDone(resultSet);
OnConsoleWriting(string.Format("[READING] Lecture réussie : {0} = {1}", ((Dp_Data)resultSet.Item[0]).UCPTname, ((Dp_Data)resultSet.Item[0]).UCPTvalue[0].Value), ILonConnectorConsoleResultType.RESULT);
return resultSet; // HERE, I WANT TO RETURN RESULT
}
catch (Exception e)
{
OnConsoleWriting(e.ToString(), ILonConnectorConsoleResultType.ERROR);
}
finally
{
OnProgressBarUpdate(progressBar.Invisible);
}
}
}
I'm trying to execute a SOAP request and to send the result. To make the application "non-blocking", i've used Thread.
The soap request works perfectly, but i have trouble to send a result just after the end of the thread. I will have to keep track of it and when the thread is over, to send back the result to another class.
How can i perform this ?
Well.. this solution CAN BE WRITTEN WAY BETTER.. but I'm currently working with the code you already posted.
The approach - using a callback (with a delegate):
First - declare a delegate in your class scope
private Action<Item_DataColl> OnResultSetComplete;
Second - assign the delegate with some function BEFORE calling your thread
public Item_DataColl invoke_command_READ(string UCPTName)
{
OnResultSetComplete = (resukts) =>
{
// DO SOMETHING HERE
};
networkingThread = new Thread(new ParameterizedThreadStart(thread_command_READ));
networkingThread.Start(new readingThreadParameter(UCPTName));
}
Finally - change your read result function:
private Item_DataColl thread_command_READ_result(object parameter)
{
readingThreadParameter p = parameter as readingThreadParameter;
Item item;
if (p.UCPT_Name != null)
{
Item_DataColl resultSet;
try
{
OnProgressBarUpdate(progressBar.UnknownEnd);
resultSet = connector.command_READ(p.UCPT_Name);
readOperationDone(resultSet);
OnConsoleWriting(string.Format("[READING] Lecture réussie : {0} = {1}", ((Dp_Data)resultSet.Item[0]).UCPTname, ((Dp_Data)resultSet.Item[0]).UCPTvalue[0].Value), ILonConnectorConsoleResultType.RESULT);
if (OnResultSetComplete != null) OnResultSetComplete(resultSet);
}
catch (Exception e)
{
OnConsoleWriting(e.ToString(), ILonConnectorConsoleResultType.ERROR);
}
finally
{
OnProgressBarUpdate(progressBar.Invisible);
}
}
}

Callback problems with WPF

I've been running into a Callback problems with async programming in WPF .Net 4.5.
There should be a way of doing this code in a more understandable way (I have suppressed a big part of it to make it easier to see what is going on).
I don't think there is a way to simply remove code because I need to call Dispatcher in order to manipulate WPF controls and calls like in TbSequence.Focus() and Utils.ShowMessageBox.
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
Controller.Busy = true;
System.Threading.Tasks.Task.Run(() =>
{
try
{
Controller.SaveItem();
}
catch (BdlDbException ex)
{
if (ex.ExceptionSubType == DbExceptionSubTypes.UniqueViolation)
{
HandleUniqueViolation(ex);
}
else
{
string errorMessage = "";
errorMessage = ex.Message;
Dispatcher.BeginInvoke(new Action(() => Utils.ShowMessageBox(t_MessageBox.Attention, errorMessage)));
}
}
Controller.Busy = false;
});
}
catch (FieldException ex)
{
if (ex.FieldName == "FirstName")
{
TbFirstName.ValidationError = true;
TbFirstName.ApplyErrorToolTip(ex.Message);
}
}
}
public void Init(UcEdit container, Employee entity = null)
{
Controller.Busy = true;
System.Threading.Tasks.Task.Run(() =>
{
try
{
Controller.Init(entity);
}
catch (BdlEntryNotFoundException ex)
{
HandleNotFoundException(ex);
}
Container.Dispatcher.BeginInvoke(new Action(() =>
{
Container.DataContext = Controller;
// Instructions order for focusing TbSequence after load should be different in case we have an existent item
// because we have to focus the control AFTER it is filled with data, in order to set the caret position correctly.
if (Controller.IsNewItem)
{
this.DataContext = Controller;
TbSequence.Focus();
Controller.Busy = false;
}
else
{
TbSequence.TextChanged += TbSequence_TextChanged;
this.DataContext = Controller;
SetButtons();
}
}));
});
}
private void HandleUniqueViolation(BdlDbException ex)
{
string errorMessage = "";
bool isSequence = false; // if true, it's name.
if (ex.Fields[1] == "Sequence")
{
errorMessage = "There is already an Employee with the sequence \"" + Controller.Item.Sequence + "\".";
isSequence = true;
}
else
{
errorMessage = "There is already an Employee named \"" + Controller.Item.FirstName +
" " + Controller.Item.LastName + "\".";
}
errorMessage += "\r\nLoad it from Database?\r\n(All the changes on this window will be lost.)";
Dispatcher.BeginInvoke(new Action(() =>
{
MessageBoxResult res = Utils.ShowMessageBox(t_MessageBox.Question, errorMessage, MessageBoxButton.YesNo);
switch (res)
{
case MessageBoxResult.Yes:
if (isSequence)
{
System.Threading.Tasks.Task.Run(() =>
{
Controller.GetEmployeeBySequence(Controller.Item.Sequence);
Init(Container, Controller.OriginalItem);
});
}
else
{
System.Threading.Tasks.Task.Run(() =>
{
Controller.GetEmployeeByName(Controller.Item.FirstName, Controller.Item.LastName);
Init(Container, Controller.OriginalItem);
});
}
break;
case MessageBoxResult.No:
break;
}
}));
}
As you can see, there is a major Callback problem here that behaves like this:
Save_Executed (UI) -> HandleUniqueViolation (Task) -> ShowMessageBox (UI) -> Controller.GetEmployeeBySequence (Task) -> Controller.Init ...
And so it goes.
Is there a way to make this code more easy to read?
Thank you.
You're starting off your code by putting (more or less) the entirety of your method body in a call to Task.Run and then explicitly marshalling to the UI thread all over the place within that callback.
Just narrow the scope of your Task.Run call so that your UI code is just outside the call to Run rather than inside both it and a call to Invoke:
private async void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
Controller.Busy = true;
try
{
await Task.Run(() => Controller.SaveItem());
}
catch (BdlDbException ex)
{
if (ex.ExceptionSubType == DbExceptionSubTypes.UniqueViolation)
{
HandleUniqueViolation(ex);
}
else
{
string errorMessage = "";
errorMessage = ex.Message;
Utils.ShowMessageBox(t_MessageBox.Attention, errorMessage);
}
}
Controller.Busy = false;
}
catch (FieldException ex)
{
if (ex.FieldName == "FirstName")
{
TbFirstName.ValidationError = true;
TbFirstName.ApplyErrorToolTip(ex.Message);
}
}
}
Here you're running the actual long running business operation that you have in a thread pool thread, but doing all of your error handling in the UI thread.
You can do the same thing throughout your application. Rather than putting everything into a background thread and then explicitly marshaling, just only ever execute operations in a background thread that should be entirely executed in a background thread.

WinRT - MessageDialog.ShowAsync will throw UnauthorizedAccessException in my custom class

I Want to write my own control, when the ctor is invoked, a MessageBox is shown.
public class Class1
{
public Class1()
{
ShowDialog();
}
void ShowDialog()
{
SynchronizationContext context = SynchronizationContext.Current;
if (context != null)
{
context.Post((f) =>
{
MessageDialog dialog = new MessageDialog("Hello!");
dialog.ShowAsync();
}, null);
}
}
}
If my class is used by someone, and write the codes as below, UnauthorizedAccessException is always thrown in dialog.ShowAsync();
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ClassLibrary1.Class1 c1 = new ClassLibrary1.Class1();
MessageDialog dialog1 = new MessageDialog("");
dialog1.ShowAsync();
}
Is there a way to show a message dialog without exception?
I found a way, enjoy it!
Task ShowDialog()
{
CoreDispatcher dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
Func<object, Task<bool>> action = null;
action = async (o) =>
{
try
{
if (dispatcher.HasThreadAccess)
await new MessageDialog("Hello!").ShowAsync();
else
{
dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => action(o));
}
return true;
}
catch (UnauthorizedAccessException)
{
if (action != null)
{
Task.Delay(500).ContinueWith(async t => await action(o));
}
}
return false;
};
return action(null);
}
As MessageDialogue needs to run on the UI thread, can you try switching it to:
var dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
dispatcher.RunAsync(DispatcherPriority.Normal, <lambda for your code which should run on the UI thread>);
The cleaner solution may look like this. The interesting part ist hidden in die showDialogAsync(). For convenience you can use the Close() method to close the current dialog again programmatically. The IUIDispatcher is another helper interface you can rebuild yourself easily:
private readonly IUIDispatcher _dispatcher;
readonly Object _queueMonitor = new object();
readonly Object _showMonitor = new object();
private IAsyncOperation<IUICommand> _currentDialogOperation;
readonly Queue<MessageDialog> _dialogQueue = new Queue<MessageDialog>();
public async Task ShowAsync(string content)
{
var md = new MessageDialog(content);
await showDialogAsync(md);
}
public async Task ShowAsync(string content, string caption)
{
var md = new MessageDialog(content, caption);
await showDialogAsync(md);
}
public async Task<MessageDialogResult> ShowAsync(string content, MessageDialogType dialogType)
{
var messageDialogResult = await ShowAsync(content, null, dialogType);
return messageDialogResult;
}
public async Task<MessageDialogResult> ShowAsync(string content, string caption, MessageDialogType dialogType)
{
var result = MessageDialogResult.Ok;
var md = string.IsNullOrEmpty(caption) ? new MessageDialog(content) : new MessageDialog(content, caption);
switch (dialogType)
{
case MessageDialogType.Ok:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
md.CancelCommandIndex = 0;
md.DefaultCommandIndex = 0;
break;
case MessageDialogType.OkCancel:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
case MessageDialogType.YesNo:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
case MessageDialogType.YesNoCancel:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
default:
throw new ArgumentOutOfRangeException("dialogType");
}
await showDialogAsync(md);
return result;
}
/// <summary>
/// Shows the dialogs, queued and one after the other.
/// We need this as a workaround for the the UnauthorizedAcsess exception.
/// </summary>
/// <param name="messageDialog">The message dialog.</param>
/// <returns></returns>
async Task showDialogAsync(MessageDialog messageDialog)
{
//Calls this function in a separated task to avoid ui thread deadlocks.
await Task.Run(async () =>
{
lock (_queueMonitor)
{
_dialogQueue.Enqueue(messageDialog);
}
try
{
while (true)
{
MessageDialog nextMessageDialog;
lock (_queueMonitor)
{
if (_dialogQueue.Count > 1)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Next dialog is waiting for MessageDialog to be accessable!!");
Monitor.Wait(_queueMonitor); //unlock and wait - regains lock after waiting
}
nextMessageDialog = _dialogQueue.Peek();
}
var showing = false;
_dispatcher.Execute(async () =>
{
try
{
lock (_showMonitor)
{
showing = true;
_currentDialogOperation = nextMessageDialog.ShowAsync();
}
await _currentDialogOperation;
lock (_showMonitor)
_currentDialogOperation = null;
}
catch (Exception e)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | " + e);
}
lock (_showMonitor)
{
showing = false;
Monitor.Pulse(_showMonitor); //unlock and wait - regains lock after waiting
}
});
lock (_showMonitor)
{
if (showing)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Waiting for MessageDialog to be closed!!");
//we must wait here manually for the closing of the dialog, because the dispatcher does not return a waitable task.
Monitor.Wait(_showMonitor); //unlock and wait - regains lock after waiting
}
}
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | MessageDialog was closed.");
return true;
}
}
finally
{
//make sure we leave this in a clean state
lock (_queueMonitor)
{
_dialogQueue.Dequeue();
Monitor.Pulse(_queueMonitor);
}
}
});
}
public void Close(string keyContent="")
{
try
{
if (keyContent.IsNullOrEmpty())
{
lock (_showMonitor)
{
if (_currentDialogOperation == null) return;
_currentDialogOperation.Cancel();
_currentDialogOperation = null;
}
}
else
{
var cancel = false;
lock (_queueMonitor)
{
if (_dialogQueue.Count == 0)
return;
var currentDialog = _dialogQueue.Peek();
Debug.WriteLine("MessageDialogService.cs | Close | {0}", currentDialog.Content);
if (currentDialog.Content == keyContent)
{
cancel = true;
}
}
if (!cancel) return;
lock (_showMonitor)
{
if (_currentDialogOperation == null) return;
_currentDialogOperation.Cancel();
_currentDialogOperation = null;
}
}
}
catch (Exception e)
{
Debug.WriteLine("MessageDialogService.cs | Close | " + e);
}
}
I think I've found it. I had the same problem when creating messageboxes in any other threads besides the main thread.
This is the C++ solution but I think you can convert it easily ;)
IAsyncOperation<IUICommand^> ^Op = msgbox->ShowAsync();
task<IUICommand^>( Op ).then([=](IUICommand^ C)
{
}).then([](task<void> t)
{
try
{
t.get();
}
catch (Platform::Exception ^e)
{
//ERROR!
}
});
On a side note this is the correct way to handle ANY WinRT/Windows 8 Store C++ exception.
You can always use
Execute.OnUIThread( async () => {
...
var dialog = new MessageDialog(yourMessage);
await dialog.ShowAsync();
...
});
This doesn't solve race conditions in the UI if you are trying to launch multiple dialogs from background threads. But you could use a try/catch to make sure you cover for that case.

Categories

Resources