I have a basic Winform app (.Net 4.6) where the main Form consists of only one docked panel. The form acts as the screen manager and will change the screen by adding/removing custom UserControls to/from the panel.
To put it simply, I have a button in the processing screen where if you click on it, will trigger a simple callback:
private void langButton_Click(object sender, EventArgs e)
{
ActionCallback?.Invoke(UserAction.BackToTitle);
}
This will call a method in the main form and eventually:
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
Now I have checked that the CurrentUICulture changes as expected but as soon as it goes back to the UserControl, which is after the invoke line, the CurrentUICulture magically reverts back to whatever it used to be before executing the delegate! (no other code changes the culture)
What's even more baffling is similar code for my title screen works fine.
They both run on UI thread.
I have been searching online and also tried many things including locking the line that is changing the culture in case the value was cached or something but no luck.
Obviously a lot of code is omitted but that's the gist of it. I could purposely pass the culture back and assign it again but that would just be bad.
Anyone could shed some light on this behaviour and possibly suggest some solutions?
EDIT 1:
I think I have finally found the issue. The callback was something like this:
private async void HandleUserAction(UserAction action)
{
switch (action)
{
case UserAction.BackToTitle:
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
break;
//other cases omitted
}
}
I basically did a quick test and extracted this to another anonymous function but without the async :
screen.ActionCallback2 += (a) => { System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");};
and it finally works!
I am not sure if it is duplicate of the post below as I am already using .Net 4.6 or above but I have to do some more reading.
Keep CurrentCulture in async/await
I won't answer my own question yet as I am still trying to grasp why this is happening, if anyone else can explain this, feel free to do so.
EDIT 2:
I found that the issue can be reproduced with the code below
private async void button16_Click(object sender, EventArgs e)
{
//will revert back to previous value once it exits the function
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("jp");
}
private void button15_Click(object sender, EventArgs e)
{
//it will be changed to jp
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("jp");
Task.Run(() =>
{
this.Invoke((MethodInvoker)delegate
{
//this will be changed back to jp after exiting the thread, even though its same UI thread
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
});
});
}
In the end I fixed it by adding this to my app.config
<AppContextSwitchOverrides value="Switch.System.Globalization.NoAsyncCurrentCulture=true" />
Though it is still a little strange to me especially when the function button15_Click doesn't involve async.
Related
I'm very confused because I feel like I'm using async/await in a completely typical way here, no different than I've been using it for years, and yet I'm getting the dreaded Control accessed from a thread other than the thread it was created on message all over the place in my application (an old WinForms app I'm trying to breathe new life into).
The project is in .NET Framework 4.8 and I have just converted an old form from using a BackgroundWorker to async/await, and I'm losing the SynchronisationContext after await statements in various places, one example of which is shown below. I replaced the complex chain of async code that actually runs with Task.Delay and the problem still arises. Adding ConfigureAwait(true) or ConfigureAwait(false) to Task.Delay doesn't seem to make any difference, not that I feel I should even need it in this scenario.
Grateful if somebody can point out where I'm going wrong.
private async void ListViewSelectedIndexChanged(object sender, EventArgs e) {
Debug.WriteLine(SynchronizationContext.Current != null); // true
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId); // 1
await Task.Delay(100);
Debug.WriteLine(SynchronizationContext.Current != null); // false - why?
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId); // 8
// Touching controls here throws error
}
EDIT 1: Okay, I chased the creation of the form upwards, and I've hit the root of the problem though I still don't understand why it's a problem. The problem can be reduced to this:
// MainForm designer:
this.Load += new System.EventHandler(this.MainFormLoad);
// MainForm code-behind:
private async void MainFormLoad(object sender, EventArgs e) {
var form = new BackupManagerForm();
form.Show(); // BackupManagerForm will exhibit the problem
// ... some async stuff including adding dynamic context menus to MainForm
}
The problem can be removed by removing async from the Load handler in MainForm. Now that I know that I can code around it, but I'm curious as to why that damages the SynchronizationContext so far downstream (despite the fact it seems perfectly able to add the context menus I mentioned above to MainForm for example).
EDIT 2: I just tried to reconstruct the problem in a blank WinForms problem and can't reproduce it. Making the Load handler for MainForm non-async definitely does solves the problem, but actually that's annoying because there's a load of async work I really need to do at that point.
As requested, this is how MainForm is instantiated (there's a lot of code removed for brevity here but hopefully nothing significant):
public static class App {
[STAThread]
public static void Main(string[] args) {
var builder = new ContainerBuilder();
IocConfig.RegisterDependencies(builder);
var container = builder.Build();
ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(container));
var bootstrapper = ServiceLocator.Current.GetInstance<IAppBootstrapper>();
bootstrapper.Startup();
}
public class AppBootstrapper : IAppBootstrapper {
private readonly ISettings _settings;
private readonly MainForm _mainForm;
public AppBootstrapper(MainForm mainForm, ISettings settings) {
_mainForm = mainForm;
_settings = settings;
}
public void Startup() {
// Other stuff removed but perhaps these might be relevant?
Application.DoEvents();
_settings.SaveAsync().Wait();
Application.Run(_mainForm);
}
}
EDIT 3: That Application.DoEvents() turns out to be significant. So does instantiating the form with an IoC container. If I either remove the Application.DoEvents() statement or use Application.Run(new MainForm()) the problem disappears. The Application.DoEvents() exists because of a splash screen that is shown before MainForm with splashForm.Show() followed by Application.DoEvents() - when MainForm eventually loads, it hides the splash screen on load.
I've found a good solution to the problem now. This comprehensively solves the problem without any annoying side effects as far as I can tell:
public void Startup() {
var context = SynchronizationContext.Current;
_splashForm.Show();
Application.DoEvents();
...
SynchronizationContext.SetSynchronizationContext(context);
Application.Run(_mainForm);
}
I still don't have a clear understanding of the problem, but it's obviously to do with the fact that the main form is not the first form to create a message loop. What's interesting though is how far down inside the application that manifests as a significant problem. Anyway, I'd be happy to accept an answer from anyone who can explain the phenomenon.
So using windows form builder, I have created a new form with textbox in it, calling this form as LogForm.cs, this form/class has a method called log(string text).
In my main form class (Form1.cs), I have created an instance of that form.
LogForm logForm = new LogForm();
logForm.log("Logger has started...");
and it show fine on the LogForm textbox. But when I call logForm.log("Some logging info...") On my code inside a thread, it somehow makes my application crash.
How do I deal with this? Please help me demostrate a small code.I am fairly new to C# and programming as a whole so I hope you consider.
Use/call this function in LogForm.log (btw methods in C# are usually capitalized).
private void SetText(string text)
{
Action set = () => yourTextBox.Text = text;
if (yourTextBox.InvokeRequired)
{
yourTextBox.Invoke(set);
}
else
{
set.Invoke();
}
}
If it cannot be set from the current thread yourTextBox.InvokeRequired will be true and the function will work it out. Otherwise it just sets it directly.
Inspiration from this answer at possible duplicate.
Since you are saying the problem persists I'll show a bit more code and try to expain it further.
First of all, I edited the SetText method. I added the private modifier since this function is not indended to be called anywhere outside of LogForm. I also added the curly brackets since that's my preferred style and it also makes sure that the if-statement behaves as expected.
public void Log(string message) {
SetText(message);
//do stuff
}
Both of these methods (Log and SetText) are placed inside the LogForm class. You can now call logForm.Log("Logger has started..."); from any thread as long as your form (containing the textbox) is already initialized. This usually happens in the constructor by calling InitializeComponent(); on the first line.
Without knowing more about your code this is probably as far as I can help you.
I am creating a Form when a certain event occurs. I put this created Form into a static member of the class where it is created. I debugged the code and everything works fine but the Form stays blocked and the user can't do anything in this window. It just appears with a loading animation (see picture). So nothing in the opened window is clickable, you can't even close it.
class CallManagementObserver : CallObserver
{
private static FrmIncomingCall frmCurrentCall;
public CallManagementObserver()
{
}
public void callChangedEvent(CallEv[] events)
{
foreach (CallEv currentEvent in events)
{
switch (currentEvent.getID())
{
case TermConnRingingEv.ID:
// Incoming call
frmCurrentCall = new FrmIncomingCall(currentEvent);
frmCurrentCall.Show();
frmCurrentCall.Update();
break;
case CiscoCallInfoChangedEv.ID:
// User accepted external call on terminal
frmCurrentCall.Close();
break;
case TermConnActiveEv.ID:
// User is in call
frmCurrentCall.Close();
break;
case ConnDisconnectedEv.ID:
// Caller has hung up
frmCurrentCall.Close();
break;
default:
break;
}
}
}
}
}
As you can see above I wrote my own Form class whose code is here:
public partial class FrmIncomingCall : Form
{
Call incomingCall;
CallEv currentEvent;
public FrmIncomingCall(CallEv currentEvent)
{
InitializeComponent();
this.currentEvent = currentEvent;
this.incomingCall = currentEvent.getCall();
}
private void initGui()
{
Connection[] callConnections = incomingCall.getConnections();
Address caller = callConnections[1].getAddress();
lblIncomingCallSource.Text = caller.getName();
}
private void btnAcceptCall_Click(object sender, System.EventArgs e)
{
TermConnEv termConnEv = (TermConnEv)currentEvent;
TerminalConnection termConn = termConnEv.getTerminalConnection();
termConn.answer();
}
private void frmIncomingCall_Load(object sender, System.EventArgs e)
{
initGui();
}
}
When I show the Form via ShowDialog() it is usable but the program stops (since this is what dialogs are made for I guess).
Any ideas what I'm doing wrong? Nothing freezes, the program is running correctly.
Well, your application is poorly designed... It seems that you have no idea of what multithreading is and why you should use it.
If the application hangs forever, then either there is a deadlock (something like the dialog wait on the calling system and the calling system wait on the dialog).
As I have no idea what CallEv is and how it is intended to be used.
Well, if the calling system works and the UI is never updated, then obviously, you never let the UI have time to be updated because your UI thread is 100% of the time using the calling system or waiting on it.
That means that the calling system should probably be used from another thread and that you should have some communication between both threads...
It might also be possible that the calling system might be used in many different ways (as it would be the case for serial port and TCP communication) where one could use what fit most with his application.
Another problem with your code is that when you close a dialog, as far as I know it cannot be used anymore without recreating the dialog as the dialog would be disposed... So you would need to set the formCurrentCall to null and update any affected code. Alternatively, you might hide the form instead and show it again when required.
In any case, it is hard to help you because we don't have any idea of what is CallEv and other classes or events in your code. Also, we have no idea which code is executing when the UI is not responding (or updated). So the question do not have enough informations. In fact, such problem are way easier to debug using a debugger as it is far easier to see what code is run and which line of code take time to execute or even to see which code is not executed.
I am already using backgroundworker.RunAsyn() to run my code on a separate thread. However I am hitting a portion where the code iterates to the next line before the previous line is completed. Should I create a separate backgroundworker to handle that? Or should I use Application.Wait() or Thread.Sleep() I am not sure the amount of time to delay and I'd rather not have my program just sitting around waiting for extra un-needed time so I am not sure which route to take. Here is a snippet of the trouble-maker.
public Form_Main()
{
InitializeComponent();
backgroundworker1.WorkerReportsProgress = true;
backgroundworker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1_ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
private void btnOpenRefreshSave_Click()
{
backgroundWorker1_RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Excel.Application exApp;
Excel._Workbook exBook;
Excel._Worksheet exSheet;
exBook = (Excel._Workbook)(exApp.WOrkbooks.Open("C:\\Book1.xlsx"));
exSheet = (Excel._Worksheet)(exBook.ActiveSheet);
//This is the line of code that often times takes a while
exBook.RefreshAll();
//end of trouble line
exBook.SaveAs("C:\\Updated_Book1.xlsx");
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
A few things come to mind on what to do here. You could try using something similar to the below
if (Application.CalculationState === xlDone Then
everything is finished calculating[enter link description here][1]
Another option would be (as others have suggested) changing the background refresh property. A quick scan of the workbooks could programmatically change that for you
foreach (Wrksheet ws in workbook.wss)
{
foreach (QueryTable table in ws.QueryTables)
table.BackgroundQuery = false;
}
workbook.RefreshAll();
The problem is caused because RefreshAll is running on a background thread. So basically you have your own backgroundworker running and another one you did not anticipate for.
The documentation for refreshAll says :
Objects that have the BackgroundQuery property set to true are refreshed in the background.
So you can get out of this problem only be setting that property to false. Then the refreshall would run in the context of your backgroundworker which is what your intent is.
If this still does not work, then you have to rethink your logic and look for an event of some kind that is triggered when the refresh is done. If this does not exist, then there is no solution other than a sleep, but that is not a good solution at all because you don't know how long to sleep.
Why do you want to delay something, can't you do saving your workbook on one of its events like SheetCalculate (Occurs after any worksheet is recalculated or after any changed data is plotted on a chart) and setting some flag in your code and reset that on that event (or any more relevant event)
I'm trying to add share functionality to my Windows Phone App. The code behaves in an unpredictable way. Sometimes it works, but mostly it doesn't and I haven't been able to get any details about what's causing the crash. Could someone please go through the code below and let me know if I've missed something? Thanks!
public ArticlePage()
{
this.InitializeComponent();
//..
RegisterForShare();
}
private void RegisterForShare()
{
DataTransferManager dataTransferManager = DataTransferManager.GetForCurrentView();
dataTransferManager.DataRequested += new TypedEventHandler<DataTransferManager,
DataRequestedEventArgs>(this.ShareLinkHandler);
}
private void ShareLinkHandler(DataTransferManager sender, DataRequestedEventArgs e)
{
DataRequest request = e.Request;
DataRequestDeferral defferal = request.GetDeferral();
request.Data.Properties.Title = this.article.Title;
request.Data.Properties.Description = this.article.Summary;
request.Data.SetWebLink(new Uri(this.article.UrlDomain));
defferal.Complete();
}
private void ShareCommand_Click(object sender, RoutedEventArgs e)
{
DataTransferManager.ShowShareUI();
}
UPDATE
The code always works while I'm debugging from visual studio but pretty much never otherwise. I made a release build thinking there might be some code in the debug build which is causing the problem but that didn't make any difference.
I also had that problem recently. The share UI crashes when one of the important parameters is not set. In your case I'd suspect that
this.article.UrlDomain
is null or not a valid Uri pattern. You should build an if-clause around it and make sure that you're dealing with a real Uri. To test your code you should insert hardcoded constants and run it again. If it doesn't crash, check your Title, Summary and UrlDomain one by one.
Other places to investigate:
Try adding your handler in the OnNavigatedTo method and remove it when you're leaving the page
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested += SharePage_DataRequested;
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
DataTransferManager.GetForCurrentView().DataRequested -= SharePage_DataRequested;
}
I also searched my code and looked at official samples again and did not find any defferals. Just to be sure - if I were you I'd strip all unnessecary lines in my code and get it as closest as possible to the official examples and then extend it back to where it was from there which is why I would comment out these two lines as well:
void SharePage_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
DataRequest request = e.Request;
//DataRequestDeferral defferal = request.GetDeferral();
request.Data.Properties.Title = this.article.Title;
request.Data.Properties.Description = this.article.Summary;
request.Data.SetWebLink(new Uri(this.article.UrlDomain));
//defferal.Complete();
}
Okay, I had the same problem. ShowShareUi actually suspends your app. If you try suspending your app you would get the error. It is actually the serialization problem.
If you want to look into the error, then while debugging, press the lifecycle events and suspend, you will crash in debug mode now.
If you are navigating between pages with a custom class you would get error. *My suggestion is that you would convert to jsonstring and send and get it back.*
I've faced similar problem (crash on ShowShareUI).
After very long investigations I've found, that this appears because unhandled exception in SaveFrameNavigationState (SuspensionManager class from template project).
In my case it was because SessionStateForFrame method failed on parsing class that couldn't be serialized.
Check out what you're saving in page state on SaveState of the page.
It happens not only on ShowShareUI but in suspend mode generally.