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.
Related
I have this file that hangs, i have some idea that it is caused by the async task, but not sure how to fix it and debugging doesnt really work since it works correctly on visual studio but fails on IIS proper..
I tried to use ConfigureAwait(false); which i read somewhere should resolve the issue but to no avail,
I also tried other methods to set my response.redirect in my other pages and complete the request, but also to no avail..
Response.Redirect("Project.aspx", false);
Context.ApplicationInstance.CompleteRequest();
Here is my code.. appreciate if someone can point me to the right direction?
protected async void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
try
{
LoggingController.log("Before Init Project");
await InitProject().ConfigureAwait(false); ;
LoggingController.log("After Init Project");
}
catch (Exception ex)
{
LoggingController.log(ex);
}
}
else
{
if (Request.Params["__EVENTTARGET"].Equals("RedirectBoard"))
{
await Task.Run(() => gotoProject(Request.Params["__EVENTARGUMENT"])).ConfigureAwait(false); ;
}
}
}
protected async Task InitProject()
{
try
{
// Login - temporary for fast testing
if (Session["user"] == null)
{
Response.Redirect("Login.aspx", false);
Context.ApplicationInstance.CompleteRequest();
}
else
{
user = (Model.UserModel)Session["user"];
}
// Init variables
int userID = user.User_ID;
int role = user.Role;
lists = new StringBuilder("const userID=" + userID + ";");
// Start tasks
// var timer = System.Diagnostics.Stopwatch.StartNew();
// Task[] task = new Task[2];
Task task0 = Task.Run(() => loadProject(userID, role));
Task task1 = Task.Run(() => loadUser(userID));
// Profile area
var profile = (Image)Master.FindControl("profile");
profile.ToolTip = user.Name;
profile.ImageUrl = user.Avatar;
new LanbanAuthentication().Authenticate(Response, user);
// Wait all tasks to be completed
await Task.WhenAll(task0, task1).ConfigureAwait(false);
ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "projectScript", lists.ToString(), true);
}
catch (Exception ex)
{
LoggingController.log(ex.Message + " # " + ex.Source + Environment.NewLine + ex.StackTrace);
}
}
//1. Load all projects
protected async Task loadProject(int userID, int role)
{
try
{
// Fetch all projects belong to or under supervised of this user
ProjectAccess myAccess = new ProjectAccess();
myAccess.fetchProject(userID, role);
var project = myAccess.MyDataSet.Tables["Project"];
Task task1 = Task.Run(() =>
{
for (int i = 0; i < project.Rows.Count; i++)
{
var row = project.Rows[i];
if (row != null)
projectbrowser.Controls.Add(ProjectBox(row));
}
});
// Send project list in JSON to client for further processing
Task task2 = Task.Run(() =>
{
StringBuilder projectList = new StringBuilder("var projectList = ");
if (project != null)
{
projectList.Append(JsonConvert.SerializeObject(project));
projectList.Append(";");
lists.Append(projectList);
}
});
await Task.WhenAll(task1, task2).ConfigureAwait(false); ;
myAccess.Dipose();
}
catch (Exception ex)
{
LoggingController.log(ex.Message + " # " + ex.Source + Environment.NewLine + ex.StackTrace);
}
LoggingController.log("loadProject completed");
}
private void loadUser(int userID)
{
// Fetch data from database
ProjectAccess myAccess = new ProjectAccess();
myAccess.fetchSharedProjectUser(userID);
var user = myAccess.MyDataSet.Tables["User"];
// Send user list in JSON to client for further processing
StringBuilder userList = new StringBuilder("var userList = ");
userList.Append(JsonConvert.SerializeObject(user));
userList.Append(";");
lists.Append(userList);
myAccess.Dipose();
LoggingController.log("loadUser completed");
}
One of my Async Handler
/// Async handler for request from client
public class ProjectHandler : IHttpAsyncHandler, IReadOnlySessionState
{
public bool IsReusable { get { return false; } }
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, Object state)
{
ProjectHandlerOperation operation = new ProjectHandlerOperation(callback, context, state);
operation.QueueWork();
return operation;
}
public void EndProcessRequest(IAsyncResult result)
{
}
public void ProcessRequest(HttpContext context)
{
throw new InvalidOperationException();
}
}
I am trying to download minecraft files libraries and assets using listbox to select version then download by click on the button but when I click on the button I get this error
"System.FormatException: 'Input string was not in a correct format.'"
on await MCDownload(files, ctoken.Token);
please reply what should I do.
Thanks!
private async void LaunchMinecraft1_Click(object sender, RoutedEventArgs e)
{
if (Directory.Exists(jsonloc + #"\versions\" + versionsList.SelectedItem.ToString()))
{
MessageBox.Show("Version already exists");
return;
}
ctoken = new CancellationTokenSource();
var files = await GetFiles(versionsList.SelectedItem.ToString(), totalPercentage.Content);
await MCDownload(files, ctoken.Token);
}
public List<string[]> urls = new List<string[]>();
public async Task MCDownload(List<string[]>urls, CancellationToken _ctsblock)
{
totalMB = 0;
_cts = new CancellationTokenSource();
sw.Start();
int count = urls.Count;
if (urls != null && urls.Count != 0)
{
System.Timers.Timer myTimer = new System.Timers.Timer();
myTimer.Elapsed += (s, e) =>
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
progressBar.Visibility = Visibility.Visible;
downloadedMB.Content = string.Format("{0} MB", ((totalMB / 1024f) / 1024f).ToString("0.00"));
}));
};
myTimer.Interval = 100; // 1000 ms is one second
myTimer.Start();
ServicePointManager.DefaultConnectionLimit = Int32.Parse(Settings.Default["download_threads"].ToString());
var block = new ActionBlock<string[]>(async url =>
{
try
{
await DownloadLibraries(url);
}
catch (WebException) { }
catch (OperationCanceledException) { }
catch (Exception e)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
MessageBox.Show("There may have been some errors while downloading the game. It shouldn't be a problem though. If you experience any issue, try to reinstall again or contact us!");
}));
Console.WriteLine("ERRORE IN" + url[3] + "\r\n" + e);
}
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Int32.Parse(Properties.Settings.Default["download_threads"].ToString()), CancellationToken = _ctsblock });
foreach (var url in urls)
{
block.Post(url);
}
block.Complete();
try
{
await block.Completion;
}
catch (TaskCanceledException)
{
return;
}
myTimer.Stop();
Application.Current.Dispatcher.Invoke(new Action(() =>
{
downloadedMB.Content = null;
}));
progressBar.Value = 100;
progressBar.Visibility = Visibility.Hidden;
return;
}
}
I have a Form with two buttons (Start , Stop).
When I press Start Button a Task is initialized and a function is called that keeps running until Stop button is pressed.
But When I press Stop button Form freezes. why?
I have copied snippet from StackOverflow but it still freezes Form.
So tell me how to Cancel Task properly?
public partial class Form1 : Form
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private Task _task;
public Form1()
{
InitializeComponent();
}
//Funtion that runs when Task is initialized.
private void EventLoop(CancellationToken token)
{
//Code here..
while (!token.IsCancellationRequested)
{
//Code here..
}
if (token.IsCancellationRequested)
{
MessageBox.Show("Operation Complete..!!");
}
}
//Function for Start Button.
private void Start_Click(object sender, EventArgs e)
{
_task = Task.Factory.StartNew(() => EventLoop(_cts.Token), _cts.Token);
}
//Function for Stop button.
private void Stop_Click(object sender, EventArgs e)
{
_cts.Cancel();
}
}
Similar Example from MSDN:
var compute = Task.Factory.StartNew(() =>
{
return SumRootN(j);
}, tokenSource.Token);`
Form After Stop button is pressed.
token.IsCancellationRequested is true .
Full EventLoop() Function.
private void EventLoop(CancellationToken token)
{
SerialPort sp = new SerialPort();
string text, batch, part, courseName;
text = batch = part = courseName = "";
int courseId = 0;
this.Invoke((MethodInvoker)delegate()
{
text = portCB.SelectedItem.ToString();
batch = comboBox2.SelectedItem.ToString();
part = comboBox3.SelectedItem.ToString();
courseName = comboBox1.SelectedItem.ToString();
progressBar1.Value = 20;
using (Model1 db = new Model1())
{
courseId = db.Courses.Where(c => c.Course_name.ToUpper() == courseName.ToUpper()).Select(c => c.Course_Id).Single();
}
});
sp.PortName = text;
sp.BaudRate = 9600;
sp.Open();
while (!token.IsCancellationRequested)
{
text = sp.ReadLine();
if (text.Contains("Found ID #"))
{
this.Invoke((MethodInvoker)delegate()
{
textBox2.Clear();
textBox2.Text = "Getting Registation ID.\n";
progressBar1.Value = 60;
});
string splitText = text.Split('#')[1];
int end = splitText.IndexOf(' ');
int id = int.Parse(splitText.Substring(0, end));
using (Model1 db = new Model1())
{
var result = db.Students.Where(s => s.Reg_Id == id && s.Batch == batch && s.Class == part).Select(s => s).SingleOrDefault();
if (result != null)
{
Attendance a = new Attendance();
a.Course_Id = courseId;
a.Student_Id = id;
a.Status = "P";
a.Date = DateTime.Today.Date;
a.Batch = batch;
a.Part = part;
db.Attendances.Add(a);
string message = "";
if (db.SaveChanges() != 0)
{
message = "Attendance Uploaded..!!\n";
}
else
{
message = "Attendance Not Uploaded ..!!\n";
}
this.Invoke((MethodInvoker)delegate()
{
progressBar1.Value = 100;
textBox2.AppendText(message);
});
}
else
{
this.BeginInvoke((MethodInvoker)delegate()
{
textBox2.AppendText("Student Does not belong to Specified Batch Or Part..\n");
});
}
}
}
else
{
this.Invoke((MethodInvoker)delegate()
{
textBox2.AppendText("No Match Found..!! \n");
});
}
this.Invoke((MethodInvoker)delegate()
{
textBox1.AppendText(text + "\n");
});
}
sp.Close();
// This exception will be handled by the Task
// and will not cause the program to crash
if (token.IsCancellationRequested)
{
//MessageBox.Show("Operation Comptele..!!");
}
}
You call MessageBox.Show("Operation Complete..!!"); in the progress of cancellation. This is highly not recommended, not talking about that you are calling an UI operation from other than the UI thread.
Comment the MessageBox.Show("Operation Complete..!!"); line out
* Edit *
Question author's comment on his original question, found the mistake, which line was removed from the post. Here are my conclusion:
Always try to isolate an issue, and reproduce in its purest form. During that process you will probably diagnose and find the issues itself :-).
So if the code with issue is long to post, it is definitely not the way just deleting lines then post it. The way is deleting lines and see if the issue exist, with other words: Isolating the issue in its purest reproducable form
please use async call on your task
private async void Start_Click(object sender, EventArgs e)
{
_task = Task.Factory.StartNew(() => EventLoop(_cts.Token), _cts.Token);
await task;
}
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.
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.