Callback problems with WPF - c#

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.

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"

Multiple parallel undefined BackgroundWorker [duplicate]

This question already has answers here:
How to limit the amount of concurrent async I/O operations?
(11 answers)
Closed 1 year ago.
I copy files in a Zip archive (3td part library to get the progress) with a BackgroundWorker so I not block the UI and I can upgrade the ProgressBar or cancel the copy.
This is the code of BackgroundWorker, source, target and name are general variable:
private void _backgroundWorkerB1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
//Check if the source directory exist
if (Directory.Exists(source) == false)
{
e.Result = "NotExist";
return;
}
//If the target not exist I will create it
if (Directory.Exists(target) == false)
{
Directory.CreateDirectory(target);
}
string filename = target + "\\" +
DateTime.Now.ToString("yyyy'-'MM'-'dd'_'HH'_'mm'_'ss") +
"-" + name + ".zip";
// 3td part library
ArchivioB1.FileName = filename;
ArchivioB1.OpenArchive(System.IO.FileMode.Create);
ArchivioB1.OnOverallProgress += new BaseArchiver
.OnOverallProgressDelegate(ArchivioB1_OnOverallProgress);
// Source directory
ArchivioB1.BaseDir = source;
// Copy all files
ArchivioB1.AddFiles("*.*");
// Close
ArchivioB1.CloseArchive();
}
catch (Exception ex)
{
e.Result = "Error";
return;
}
}
I need to modify the code to make it in parallel from a DataGrid. I will have a DataGrid on the first column the name of the file, on the second a progress bar, on third the State and on fourth the destination of file.
The DataGrid can have an undefined rows and these rows can be more than cores of the CPU. How can I run the same function in parallel, and if the rows are more than the CPU cores, the system have to wait till one of it will be free and continue with the next copy?
I don't understand why the community have to close the question when isn't duplicate like they think, in any case if someone need to copy files in archive parallely with a progress
Hereafter final Edit with working code:
private async void btBackup_Click(object sender, RoutedEventArgs e)
{
btBackup.IsEnabled = false;
btCancel.IsEnabled = true;
var row_list1 = GetDataGridRows(dgwStation);
List<Task> tasks = new List<Task>();
ZipForge[] ZipList = new ZipForge[DataGridItemsList.Count];
tokenSource = new CancellationTokenSource();
foreach (DataGridRow single_row in row_list1)
{
int riga = single_row.GetIndex();
ZipList[riga] = new ZipForge();
tasks.Add(Task.Run(() => CopyTest(riga,ZipList[riga])));
}
await Task.WhenAll(tasks);
if (generalerror)
tbkStato.Text = "Completed with error";
else if (tbkStato.Text != "Cancelled")
tbkStato.Text = "Completed";
}
private void btCancel_Click(object sender, RoutedEventArgs e)
{
try
{
if (tokenSource != null) //se il token non รจ nullo, posso chiedere la cancellazione
{
//tbkStato.Text = "Cancelled";
tokenSource.Cancel();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public void CopyTest(int rowindex, ZipForge ArchivioB1)
{
string nome1 = "";
try
{
//Takes data from DatGridItemsList
string source = DataGridItemsList[rowindex].Source, target = DataGridItemsList[rowindex].Target, filename = DataGridItemsList[rowindex].FileName;
tbkStato.Dispatcher.Invoke(new Action(() => { tbkStato.Text = "Check source"; }));
if (Directory.Exists(source) == false)
{
DataGridItemsList[rowindex].State = "Not Exist";
dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
generalerror = true;
return;
}
tbkStato.Dispatcher.Invoke(new Action(() => { tbkStato.Text = "Check target"; }));
if (Directory.Exists(target) == false)
{
Directory.CreateDirectory(target);
}
DataGridItemsList[rowindex].State = "creating Zip";
dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
nome1 = target + "\\" + DateTime.Now.ToString("yyyy'-'MM'-'dd'_'HH'_'mm'_'ss") + "-" + filename + ".zip";
ArchivioB1.FileName = nome1;
ArchivioB1.OpenArchive(System.IO.FileMode.Create,FileAccess.Write);
ArchivioB1.Comment = rowindex.ToString();
ArchivioB1.OnOverallProgress += new BaseArchiver.OnOverallProgressDelegate(ArchivioB1_OnOverallProgress);
ArchivioB1.BaseDir = source;
// Copia tutti i file nell'archivio creato
ArchivioB1.AddFiles("*.*");
if (tokenSource.Token.IsCancellationRequested) //Interruzzione dell'utente
{
tokenSource.Token.ThrowIfCancellationRequested();
}
else
{
ArchivioB1.CloseArchive();
DataGridItemsList[rowindex].State = "Completed";
dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
}
}
catch (OperationCanceledException)
{
ArchivioB1.CloseArchive();
if (File.Exists(nome1))
File.Delete(nome1);
DataGridItemsList[rowindex].State = "Cancelled";
dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
tbkStato.Dispatcher.Invoke(new Action(() => { tbkStato.Text = "Cancelled"; }));
return;
}
catch (Exception ex)
{
ArchivioB1.CloseArchive();
if (File.Exists(nome1))
File.Delete(nome1);
DataGridItemsList[rowindex].State = ex.Message.ToString();
dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
generalerror = true;
return;
}
}
private void ArchivioB1_OnOverallProgress(object sender, double progress, TimeSpan timeElapsed, TimeSpan timeLeft, ProcessOperation operation, ProgressPhase progressPhase, ref bool cancel)
{
if (tokenSource.Token.IsCancellationRequested) //Interruzzione dell'utente
{
cancel = true;
//tokenSource.Token.ThrowIfCancellationRequested();
}
ZipForge Archivio = (ZipForge)sender;
int indice = Convert.ToInt32(Archivio.Comment);
DataGridItemsList[indice].Progress = Convert.ToInt32(progress);
dgwStation.Dispatcher.Invoke(new Action(() => { dgwStation.Items.Refresh(); }));
}
I would recommend using Tasks instead of Backgroundworkers.You can run as many Tasks as you wish in a simple mannner using Task.WhenAll...
If you want to execute tasks syncronously just omit await...
public async ExecuteMultipleTasksSimultaneously()
{
List<Task> tasks = new List<Task>()
{
Task.Factory.StartNew(() => ActionA()),
Task.Factory.StartNew(() => ActionB()),
Task.Factory.StartNew(() => ActionC())
};
//Execute all tasks "at the same time"...
await Task.WhenAll(tasks);
// or await Task.WhenAny(tasks);
// or await Task.WaitAll(tasks)
//or Task.WaitAny(tasks) depending on your execution needs
// Continue on this thread...
//Alternative to Task.Factory.StartNew(() => ActionA()) you could use
// Task.Run(()=> ActionA())
}
I hope this points the right direction. Best regards.

Handle Multiple Incoming Data

I'm creating a UI that uses rabbitmq to receive multiple lists of type 'StorageMessage'. I'm using a background worker to merge all the data from the lists into a datatable and I bind to the gridview in the UI. I have put the method that receives the messages below, my problem is that the UI doesn't display all the data I expect it to. I'm guessing its because the background work is busy when the message comes in. Whats the correct approach to deal with these types of situations?
public void OnMessageArrived(MessageArrivedEventArgs<object> args)
{
var topicName = args.MessagingParticipant.TopicName;
if (_logger.IsInfoEnabled)
_logger.InfoFormat("Message Received from {0}", topicName);
try
{
if (args.To == _uniqueId.ToString() || args.To.EqualsOrdinalIgnoreCase("all"))
{
lock (_lock)
{
var receivedMessage = Serialization.FromJsonString<StorageMessage>(args.MessageBody);
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync(receivedMessage);
}
}
}
}
catch (Exception exception)
{
_logger.Error("An error occurred whilst processing the received message.", exception);
}
}
The RunWorkComplete Code looks lie
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
var res = e.Result as DataTable;
_storageDataTable = ReorderTable(res, "Date", "Factor", "Aldbrough", "Holford", "Humbly Grove", "Stublach", "Holehouse Farm", "Hatfield Moor", "Hornsea", "Hill Top", "Rough", "South Hook", "Isle Of Grain", "Dragon");
UpdateDataTable();
}
catch (Exception ex)
{
}
}
And the updateDataTable code is:
private void UpdateDataTable()
{
try
{
if (InvokeRequired)
{
BeginInvoke(new MethodInvoker(() => UpdateDataTable()));
}
else
{
radGridView1.SuspendLayout();
radGridView1.GroupDescriptors.Clear();
radGridView1.DataSource = null;
radGridView1.DataSource = _storageDataTable;
radGridView1.GroupDescriptors.Add("Factor", ListSortDirection.Ascending);
// radGridView1.MasterTemplate.AutoExpandGroups = true;
radGridView1.Refresh();
radGridView1.ResumeLayout(true);
}
}
catch (Exception exception)
{
_logger.Error("Encountered an error while updating the data table.", exception);
}
}
Thanks
Elias

How to keep a thread on hold in a method untill another therad Ends in C#

I have 2 Methods, One method call the other. thread is declared inside the 2nd method and that method will return a boolean output. so when i call the 2nd method i cant control the output, and 1st method returns the success message before the thread ends. I want the boolean output after when the thread ends. How can i control this?
1st Method
private void AccessElements()
{
TaxoProcess Taxo = new TaxoProcess();
if (Taxo.AccessEntity())
{
MessageBox.Show("Succesfully Extracted Data", "Extract Application", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
2nd Method,
public bool AccessEntity()
{
try
{
bool Status = true;
Thread MainThread = Thread.CurrentThread;
Thread backgroundThread = new Thread(new ThreadStart(() =>
{
for (int i = 0; i < Entities.Count; i++)
{
Thread.Sleep(100);
Dispatcher.FromThread(MainThread).BeginInvoke(new Action(() =>
{
int PercentageValue = (int)(0.5f + ((100f * i) / Entities.Count));
StaticDataProperties.ProgBar.Value = PercentageValue;
}));
}
}));
backgroundThread.Start();
return Status;
}
catch (Exception ex)
{
ErrorException = ex.Message;
return false;
}
}
To fix your problem you could use Thread.Join and do this, place this logic before return statement..
backgroundThread.Join(); // blocks calling thread.
There's two general directions you can attempt to take:
Dirty approach:
Add a temprary boole that is true while the "inner" thread is still running and handle the logic that should be handled after this after a "while"-statement on that boole.
(the while-statement would keep the code looping/"paused" untill the boole's value is falsey)
Neat approach:
Using async tasks and a callback function.
Instead of using threads which use too much memory you can use Tasks and async/await like Charles Mager suggested
private async void AccessElements()
{
TaxoProcess Taxo = new TaxoProcess();
if (await Taxo.AccessEntity())
{
MessageBox.Show("Succesfully Extracted Data", "Extract Application", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
public async Task<bool> AccessEntity()
{
return Task.Run(() =>
{
try
{
for (int i = 0; i < Entities.Count; i++)
{
await Task.Delay(100);
int PercentageValue = (int)(0.5f + ((100f * i) / Entities.Count));
StaticDataProperties.ProgBar.Value = PercentageValue;
}
return true;
}
catch (Exception ex)
{
ErrorException = ex.Message;
return false;
}
});
}
This is shorter and does all the thread managements in the background

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