Im having this excepton in the invoke mothod and i don't know why.
System.InvalidOperationException: 'Operación no válida a través de subprocesos: Se tuvo acceso al control 'Form2' desde un subproceso distinto a aquel en que lo creó.'
Google translate this as:
System.InvalidOperationException: 'Invalid operation through threads: The' Form2 'control was accessed from a different thread than the one in which it was created.'
If I call the invoke from a button for example it works correctly but I need to call this from the FileSystemWatcher.
List<Thread> listThreads = new List<Thread>();
private void Form1_Load(object sender, EventArgs e)
{
RunFileSystemWatcher();
}
public void RunFileSystemWatcher()
{
FileSystemWatcher fsw = new FileSystemWatcher();
fsw.Path = "C:/Users/Gaming/Documents";
fsw.NotifyFilter = NotifyFilters.LastAccess;
fsw.NotifyFilter = NotifyFilters.LastWrite;
//fsw.NotifyFilter = NotifyFilters.Size;
//fsw.Created += FileSystemWatcher_Created;
fsw.Changed += FileSystemWatcher_Changed;
fsw.Filter = "*.txt";
fsw.EnableRaisingEvents = true;
}
Boolean abrir = true;
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (abrir) {
for (int i=0; i<5; i++)
{
Thread hilo = new Thread(() => showForms(new Form2()));
hilo.Start();
listThreads.Add(hilo);
abrir = false;
}
}
else{
for(int i=0; i<listThreads.Count; i++)
{
try
{
Invoke((MethodInvoker)delegate {
listForms[i].Close();
});
listThreads[i].Abort();
}
catch (ThreadAbortException)
{
}
}
}
}
List<Form2> listForms = new List<Form2>();
private void showForms(Form2 form)
{
listForms.Add(form);
form.ShowDialog();
}
You have a synchronize with the main thread UI conflict.
You must sync the call to any action on UI controls with the main thread.
You can use a BackgroundWorker.
Or this:
static public class SyncUIHelper
{
static public Thread MainThread { get; private set; }
// Must be called from the Program.Main or the Main Form constructor for example
static public void Initialize()
{
MainThread = Thread.CurrentThread;
}
static public void SyncUI(this Control control, Action action, bool wait = true)
{
if ( !Thread.CurrentThread.IsAlive ) throw new ThreadStateException();
Exception exception = null;
Semaphore semaphore = null;
Action processAction = () =>
{
try { action(); }
catch ( Exception except ) { exception = except; }
};
Action processActionWait = () =>
{
processAction();
if ( semaphore != null ) semaphore.Release();
};
if ( control != null
&& control.InvokeRequired
&& Thread.CurrentThread != MainThread )
{
if ( wait ) semaphore = new Semaphore(0, 1);
control.BeginInvoke(wait ? processActionWait : processAction);
if ( semaphore != null ) semaphore.WaitOne();
}
else
processAction();
if ( exception != null ) throw exception;
}
}
Usage:
this.SyncUI(listForms[i].Close /*, true or false to wait or not */);
And:
this.SyncUI(() => form.ShowDialog() /*, true or false to wait or not */);
With:
private void Form1_Load(object sender, EventArgs e)
{
SyncUIHelper.Initialize();
RunFileSystemWatcher();
}
You need to correct your code in FileSystemWatcher_Changed because it is buggy.
Related
I have a button that starts two threads
private void CrawdBtn_Click(object sender, EventArgs e)
{
CrawdBtn.Enabled = false;
t = new Thread(AddLinksToList);
b = new Thread(EnqueueFromList);
t.Start();
b.Start();
}
and there are another buttons to pause, Resume, Stop those threads
My question is how can I disable (pause, Resume, Stop) buttons while the threads are working and re enable Crawl after the threads finished
Here is how you could start a Thread and have a way to await its completion:
public static Thread CreateAwaitableThread(Action action, out Task threadCompletion)
{
var tcs = new TaskCompletionSource<bool>();
threadCompletion = tcs.Task;
return new Thread(() =>
{
try
{
action();
}
finally
{
tcs.SetResult(true);
}
});
}
This method returns the newly created Thread, and also a Task that will be completed when the Thread is completed. You could use it like this:
private async void CrawdBtn_Click(object sender, EventArgs e)
{
CrawdBtn.Enabled = false;
Thread t1 = CreateAwaitableThread(AddLinksToList, out var t1c);
Thread t2 = CreateAwaitableThread(EnqueueFromList, out var t2c);
t1.Start();
t2.Start();
await Task.WhenAll(t1c, t2c);
CrawdBtn.Enabled = true;
}
In case of an exception the error will not be propagated through the Task. It is assumed that the delegates already include error handling logic. If not, an unhandled exception will occur as usual.
To solve your problem you can make a thread to check the ThreadState of thread t and thread b
private void btnstart_Click(object sender, EventArgs e)
{
t = new Thread(AddLinksToList);
b = new Thread(EnqueueFromList);
t.Start();
b.Start();
if (threadchecker == null)//this if determines Whether it's the first time or not
{
threadchecker = new Thread(() => ChekingStateOfThreads());
threadchecker.IsBackground = true;
threadchecker.Start();
}
}
Since the thread wants to check the ThreadState of those threads it should always run.
and This is ChekingStateOfThreads
private void ChekingStateOfThreads()
{
while (true)
{
Thread.Sleep(1000);
if (t.ThreadState == ThreadState.Stopped)
{
this.Invoke(new Action(() =>
{
btnpause.Enabled = btnstart.Enabled = false;
btnresume.Enabled = btnstop.Enabled = true;
}));
}
else if (t.ThreadState == ThreadState.Running)
{
this.Invoke(new Action(() =>
{
btnstart.Enabled = btnresume.Enabled = false;
btnpause.Enabled = btnstop.Enabled = true;
}));
}
else if (t.ThreadState == ThreadState.Aborted)
{
this.Invoke(new Action(() =>
{
btnstart.Enabled = true;
btnpause.Enabled = btnresume.Enabled = btnstop.Enabled = false;
}));
}
else if (t.ThreadState == ThreadState.Suspended)
{
this.Invoke(new Action(() =>
{
btnpause.Enabled = btnstart.Enabled = false;
btnresume.Enabled = btnstop.Enabled = true;
}));
}
}
}
The concept of function is pretty simple. Each 1 second the thread is check the state of thread t.
See Why use Invoke on Controls in .net? to figure out why should we use INVOKE.
To abort the threadchecker just use the Form_closing Event
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
threadchecker.Abort();
}
I am trying to understand what I can and cant do with background workers. I have seen a fair amount of posts on this but it all seems to involve some operation with loops and you cancel an operation within a loop. I am wanting to find out if I can cancel some operation on a background worker without a loop.
I have the following simple form that I'm playing with:
which contains the following code:
string[,] TestData = new string[300000, 100];
List<string> TestDataList;
private static Random random = new Random();
public Form1()
{
InitializeComponent();
// Loading up some fake data
for (int i = 0; i < 300000; i++)
{
for (int j = 0; j < 100; j++)
{
this.TestData[i, j] = RandomString(10) + j.ToString();
}
}
}
public static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
which loads a string array with a lot of dummy data. The start button method is as follows:
private void StartWork_Click(object sender, EventArgs e)
{
try
{
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Complete);
bw.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show("Something went wrong.\nError:" + ex.Message);
}
}
And I also have:
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
this.TestDataList = this.TestData.Cast<string>()
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g => string.Join(",", g.Select(x => x.Item))).ToList();
}
private void bw_Complete(object sender, RunWorkerCompletedEventArgs e)
{
this.showWorkingLabel.Text = "Work done";
}
private void btnCancel_Click(object sender, EventArgs e)
{
// I want to cancel the work with this button
// Then show
this.showWorkingLabel.Text = "Work Cancelled";
}
So you'll notice that my bw_DoWork method does not contain any loops, just a single operation and I want to know if:
If I can kill/cancel the background worker by clicking the Cancel button while the following code is being executed:
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g => string.Join(",", g.Select(x => x.Item))).ToList();
Can I update the label showWorkingLabel while the background work is happening so that it continuously shows ".", "..", "..." and then back to "." like a progress bar to indicate work is still happening
You need first to support cancellation
bw.WorkerSupportsCancellation = true;
Then you need to share a cancellation token at form level
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken cancellationToken;
Inside your work you need to throw the cancellation:
cancellationToken.ThrowIfCancellationRequested();
Or handle it gracefully with the background worker even for pending cancellations: BackgroundWorker.CancellationPending
And in the cancell button you can call the cancellation like this:
cts.Cancel();
Using your code it will become something similar to the following indication, you should handle graceful cancellations:
string[,] TestData = new string[30000, 100];
List<string> TestDataList;
private static Random random = new Random();
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken cancellationToken;
private void BtnStart_Click(object sender, EventArgs e)
{
try
{
this.showWorkingLabel.Text = "Work start";
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
cancellationToken = cts.Token;
cancellationToken.Register(bw.CancelAsync);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Complete);
bw.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show("Something went wrong.\nError:" + ex.Message);
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
cancellationToken.ThrowIfCancellationRequested();
this.TestDataList = this.TestData
.Cast<string>()
.Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
.GroupBy(g => g.GroupIndex)
.Select(g =>
{
cancellationToken.ThrowIfCancellationRequested();
return string.Join(",", g.Select(x => x.Item));
})
.ToList();
}
private void btnCancel_Click(object sender, EventArgs e)
{
cts.Cancel();
this.showWorkingLabel.Text = "Work Cancelled";
}
As per the MSDN page for BackgroundWorker:
When creating the worker, you can make it support cancellation by setting
backgroundWorker.WorkerSupportsCancellation = true;
You can request cancellation by calling CancelAsync() on the BackgroundWorker.
Then your BackgroundWorker should periodically check the BackgroundWorker.CancellationPending property, and if set to true, it should cancel its operation. As Sir Rufo pointed out in a comment, don't forget to inside the DoWork delegate you have to set DoWorkEventArgs.Cancel to true.
The MSDN page I linked has additional examples of usage in real code.
Here is a working example using the BackgroundWorker builtin cancellation support.
// We need to remember the BackgroundWorker
private BackgroundWorker bw;
private void StartWork_Click( object sender, EventArgs e )
{
bw = new BackgroundWorker
{
WorkerSupportsCancellation = true,
};
bw.DoWork += Bw_DoWork;
bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
bw.RunWorkerAsync();
showWorkingLabel.Text = "Work started ...";
}
private void Bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
if ( e.Cancelled ) // was it cancelled?
{
showWorkingLabel.Text = "Work cancelled.";
return;
}
if ( e.Error != null ) // any error?
{
showWorkingLabel.Text = "Work faulted - " + e.Error.Message;
return;
}
// assign the bw Result to the field
this.TestDataList = (List<string>)e.Result;
showWorkingLabel.Text = "Work completed.";
}
private void Bw_DoWork( object sender, DoWorkEventArgs e )
{
try
{
e.Result = this.TestData
.Cast<string>()
.Select( ( s, i ) =>
{
// check for cancellation
if ( bw.CancellationPending )
throw new OperationCanceledException();
return new
{
GroupIndex = i / 100,
Item = s.Trim().ToLower()
};
} )
.GroupBy( g => g.GroupIndex )
.Select( g =>
{
// check for cancellation
if ( bw.CancellationPending )
throw new OperationCanceledException();
return string.Join( ",", g.Select( x => x.Item ) );
} )
.ToList();
}
catch ( OperationCanceledException )
{
e.Cancel = true;
}
}
private void btnCancel_Click( object sender, EventArgs e )
{
// request cancellation
bw.CancelAsync();
showWorkingLabel.Text = "Work cancellation requested ...";
}
and another doing exactly the same with the modern async/await Task and CancellationToken
private CancellationTokenSource cts;
private async void StartWork_Click( object sender, EventArgs e )
{
showWorkingLabel.Text = "Work started ...";
cts = new CancellationTokenSource();
var token = cts.Token;
try
{
TestDataList = await Task.Run( () =>
{
return this.TestData
.Cast<string>()
.Select( ( s, i ) =>
{
token.ThrowIfCancellationRequested();
return new
{
GroupIndex = i / 100,
Item = s.Trim().ToLower()
};
} )
.GroupBy( g => g.GroupIndex )
.Select( g =>
{
token.ThrowIfCancellationRequested();
return string.Join( ",", g.Select( x => x.Item ) );
} )
.ToList();
}, token );
showWorkingLabel.Text = "Work completed.";
}
catch ( OperationCanceledException )
{
showWorkingLabel.Text = "Work canceled.";
}
catch ( Exception ex )
{
showWorkingLabel.Text = "Work faulted - " + ex.Message;
}
}
private void btnCancel_Click( object sender, EventArgs e )
{
cts.Cancel();
showWorkingLabel.Text = "Work cancellation requested ...";
}
I want to change wpf controls status after click button start.
The picture is what I want.
Following is my code
private bool _bWorking = false;
public delegate void UpdateStatusDelegate();
private void SetStatus(bool bEnable)
{
if (bEnable)
{
tbName.IsReadOnly = false;
barStatus.Visibility = Visibility.Hidden;
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
btnClose.IsEnabled = true;
}
else
{
tbName.IsReadOnly = true;
barStatus.Visibility = Visibility.Visible;
btnStart.IsEnabled = false;
btnStop.IsEnabled = true;
btnClose.IsEnabled = false;
}
}
internal void UpdateStatus()
{
SetStatus(true);
_bWorking = false;
}
private void ThreadFunc()
{
//for (; ; )
//{
// // do something here
// if (_bWorking == false)
// break;
//}
Thread.Sleep(500);
this.Dispatcher.Invoke(new UpdateStatusDelegate(UpdateStatus));
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
_bWorking = true;
SetStatus(false);
this.UpdateLayout();//this.InvalidateVisual();
try
{
Thread t = new Thread(new ThreadStart(() =>
{
ThreadFunc();
}));
t.IsBackground = true;
t.Name = "test status";
t.Start();
while (t.IsAlive)
{
// wait thread exit
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
_bWorking = false;
SetStatus(true);
}
But actually after I click button start, the UI seems frozen, and the thread exited, the UI become normal.
my VS is VS2010.
Force a WPF control to refresh?
this post is not work for me.
edit summary:
add delegate void UpdateStatusDelegate() and function UpdateStatus() to my code
// wait thread exit
The whole point of a background thread is to not wait for it by blocking your UI thread. Don't.
Instead, have the thread notify your UI when it's done.
Try to use BackgroundWorker component it might help you. For more information check the MSDN article
workable code
public delegate void UpdateStatusDelegate();
private bool _bWorking = false;
private void SetStatus(bool bEnable)
{
if (bEnable)
{
tbName.IsReadOnly = false;
barStatus.Visibility = Visibility.Hidden;
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
btnClose.IsEnabled = true;
}
else
{
tbName.IsReadOnly = true;
barStatus.Visibility = Visibility.Visible;
btnStart.IsEnabled = false;
btnStop.IsEnabled = true;
btnClose.IsEnabled = false;
}
}
internal void UpdateStatus()
{
SetStatus(true);
}
private void ThreadFunc()
{
try
{
// use Stopwatch to simulate jobs
var watch = Stopwatch.StartNew();
for (; ; )
{
var elapsedMs = watch.ElapsedMilliseconds;
if (elapsedMs > 10000 // 10 seconds
|| _bWorking == false)
{
break;
}
}
watch.Stop();
this.Dispatcher.Invoke(new UpdateStatusDelegate(UpdateStatus));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
_bWorking = false;
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
try
{
_bWorking = true;
SetStatus(false);
Thread t = new Thread(new ThreadStart(() =>
{
ThreadFunc();
}));
t.IsBackground = true;
t.Name = "test status";
t.Start();
//while (t.IsAlive)
{
// wait thread exit
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
_bWorking = false;
SetStatus(true);
}
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.
This is my 1st C# project so I may be doing something obviously improper in the code below.
I am using .NET, WinForms (I think), and this is a desktop application until I get the bugs out.
UpdateGui() uses Invoke((MethodInvoker)delegate to update various GUI controls based on received serial data and
sends a GetStatus() command out the serial port 4 times a second.
Thread Read() reads the response from serial port whenever it arrives which should be near immediate.
SerialPortFixer is a SerialPort IOException Workaround in C# I found at
http://zachsaw.blogspot.com/2010/07/serialport-ioexception-workaround-in-c.html.
After one or both threads die I'll see something like
The thread 0x1288 has exited with code 0 (0x0).
in the debug code output.
Why do UpdateGui() and/or Read() eventually die?
public partial class UpdateStatus : Form
{
private readonly byte[] Command = new byte[32];
private readonly byte[] Status = new byte[32];
readonly Thread readThread;
private static readonly Mutex commandMutex = new Mutex();
private static readonly Mutex statusMutex = new Mutex();
...
public UpdateStatus()
{
InitializeComponent();
SerialPortFixer.Execute("COM2");
if (serialPort1.IsOpen)
{
serialPort1.Close();
}
try
{
serialPort1.Open();
}
catch (Exception e)
{
labelWarning.Text = LOST_COMMUNICATIONS + e;
labelStatus.Text = LOST_COMMUNICATIONS + e;
labelWarning.Visible = true;
}
readThread = new Thread(Read);
readThread.Start();
new Timer(UpdateGui, null, 0, 250);
}
static void ProcessStatus(byte[] status)
{
Status.State = (State) status[4];
Status.Speed = status[6]; // MSB
Status.Speed *= 256;
Status.Speed += status[5];
var Speed = Status.Speed/GEAR_RATIO;
Status.Speed = (int) Speed;
...
}
public void Read()
{
while (serialPort1 != null)
{
try
{
serialPort1.Read(Status, 0, 1);
if (Status[0] != StartCharacter[0]) continue;
serialPort1.Read(Status, 1, 1);
if (Status[1] != StartCharacter[1]) continue;
serialPort1.Read(Status, 2, 1);
if (Status[2] != (int)Command.GetStatus) continue;
serialPort1.Read(Status, 3, 1);
...
statusMutex.WaitOne();
ProcessStatus(Status);
Status.update = true;
statusMutex.ReleaseMutex();
}
catch (Exception e)
{
Console.WriteLine(#"ERROR! Read() " + e);
}
}
}
public void GetStatus()
{
const int parameterLength = 0; // For GetStatus
statusMutex.WaitOne();
Status.update = false;
statusMutex.ReleaseMutex();
commandMutex.WaitOne();
if (!SendCommand(Command.GetStatus, parameterLength))
{
Console.WriteLine(#"ERROR! SendCommand(GetStatus)");
}
commandMutex.ReleaseMutex();
}
private void UpdateGui(object x)
{
try
{
Invoke((MethodInvoker)delegate
{
Text = DateTime.Now.ToLongTimeString();
statusMutex.WaitOne();
if (Status.update)
{
if (Status.Speed > progressBarSpeed.Maximum)
{
Status.Speed = progressBarSpeed.Maximum;
}
progressBarSpeed.Value = Status.Speed;
labelSpeed.Text = Status.Speed + RPM;
...
}
else
{
labelWarning.Text = LOST_COMMUNICATIONS;
labelStatus.Text = LOST_COMMUNICATIONS;
labelWarning.Visible = true;
}
statusMutex.ReleaseMutex();
GetStatus();
});
}
catch (Exception e)
{
Console.WriteLine(#"ERROR! UpdateGui() " + e);
}
}
}
A thread will terminate when there's no more code to execute, or more specifically when the method you specify when you create thread returns.
Maybe serialport1 becomes null?
As for the update timer, there is a special purpose windows forms timer that runs periodically that doesn't require you to use Invoke. It's the right tool for the job