i have been developing a Windows form application and for DB end i am using Dapper.
The problem i am trying to solve is to execute a parametered stored procedure asynchronously so that my the execution of the application is not stopped and once the execution is done i should be able to show user a message
This is the function which calls the stored procedure (ExceptionData is not the usual meaning of the word. it has some other meaning in my business logic)
public async Task<bool> Load_ExceptionData(DateTime t)
{
IDbTransaction trans = null;
try
{
trans = _connection.BeginTransaction();
DynamicParameters prms = new DynamicParameters();
prms.Add("Today", t ,DbType.Date);
await _connection.ExecuteAsync("usp_CC_Load_ExceptionTable",
prms,
trans,
commandTimeout: 0,
commandType: CommandType.StoredProcedure
);
trans.Commit();
return true;
}
catch (Exception)
{
trans.Rollback();
return false;
}
}
And this function is called in trigger to a click
private async void todayToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
DateTime today;
today = Convert.ToDateTime("3/16/2016");//DateTime.Today;
CycleCountRepository _rep = new CycleCountRepository(); // Repository
todayToolStripMenuItem.Click -= todayToolStripMenuItem_Click; // Disabling the button so while in execution not clicked again
if (Enabled && _rep.Check_CycleCountToday(today)) // Checks if the data is already present for the date
{
todayToolStripMenuItem.Image = Properties.Resources.progress;
bool result = await _rep.Load_ExceptionData(today); // the async function
if (result == true)
{
ShowMessage("Data Load Successfully! Date: " + today, "Success", MessageBoxIcon.None); // This message is shown twice
}
else
{
ShowMessage("Data Load Error! Date: " + today, "Warning", MessageBoxIcon.Warning);
}
CheckTodayLoad();
}
else
{
ShowMessage("Nothing to Load! No locations scanned today!", "Warning", MessageBoxIcon.Warning);
}
}
catch (Exception ex)
{
ShowMessage(ex.Message, "Warning", MessageBoxIcon.Warning);
}
}
When i debug the code. the first time execution hits
await _rep.Load_ExceptionData(today);
it goes back again to the click event start and comes back to the await line.
After that the execution starts and once await is done the message is shown.
But after showing debugger points to
if (result == true)
line and when i step forward it goes inside it and again Shows the message
I am new to Async/await and this all may just be a dumb mistake i did. But still i will appreciate the answer
Alright guys i have figured out the problem. I will share so that no one else does the same mistake like i did.
So i had a function which was called on form load and was responsible to check whether the data which i am going to load is already present in the table. If "Yes" then remove the click event from the ToolStripMenuItem and if "No" then Add it.
What i was doing wrong was
todayToolStripMenuItem.Click += todayToolStripMenuItem_Click;
i was adding it twice to the Click
One time implicitly it was added in start of the form
When you click on the control from the design view. It adds its click even in your code and the assign it in the designer of the form
Second time i was explicitly adding it my self
check the line in the code
Solution was to remove the event before adding it to click so that it doesnt add twice
private void CheckTodayLoad()
{
DateTime today;
today = Convert.ToDateTime(DateTime.Today);
CycleCountRepository _rep = new CycleCountRepository();
if (_rep.CheckTodayLoaded(today))
{
todayToolStripMenuItem.Image = Properties.Resources.checkmark;
todayToolStripMenuItem.Text = "Today " + "[" + today.ToShortDateString() + "]" + " : Loaded";
todayToolStripMenuItem.Click -= todayToolStripMenuItem_Click;
}
else
{
todayToolStripMenuItem.Image = SystemIcons.Warning.ToBitmap();
todayToolStripMenuItem.Text = "Today " + "[" + today.ToShortDateString() + "]" + " : Not Loaded";
todayToolStripMenuItem.Click -= todayToolStripMenuItem_Click; // Just to make sure it is not registered twice. Previously this line wasnt there
todayToolStripMenuItem.Click += todayToolStripMenuItem_Click;
}
}
Cheers!
When await _connection.ExecuteAsync(...) is executed (because it, most certainly, doesn't complete synchronously), control is returned to the caller (bool result = await _rep.Load_ExceptionData(today);) which also returns control to the caller.
When connection.ExecuteAsync(...) completes, the continuation of the Load_ExceptionData method is posted to the synchronization context.
When _rep.Load_ExceptionData(today) completes, the continuation of the todayToolStripMenuItem_Click method is posted to the synchronization context.
I'm sure that's what you're seeing.
Do something like that
todayToolStripMenuItem.Click -= todayToolStripMenuItem_Click;
Thanks
Related
I have looked all over for a solution to an issue. I have noticed that in my android app, every time I fire an event from <button Clicked="GPSToggle_Clicked">, for some reason it increments the number of times my methods get called. So after I compile and load this on my phone, I hit my "GPSToggle_Clicked" button, and then to stop hit that button again. On the first "stop", I'll get a single instance of the below output:
---------------------------------------------------------------Attempting string parsing
---------------------------------------------------------------Sending string to SubmitGeneratedGPX
---------------------------------------------------------------path: /storage/emulated/0/Download/GPX/2022-10-27-02-44-06.gpx
---------------------------------------------------------------GPX File creation success
---------------------------------------------------------------:RawBufferToJsonString: [{"Timestamp":"2022-10-27T18:43:52.849+00:00","Latitude":41.5263818,"Longitude":-81.6507923,"Altitude":153.29998779296875,"Accuracy":20.0,"VerticalAccuracy":1.7990270853042603,"Speed":null,"Course":null,"IsFromMockProvider":false,"AltitudeReferenceSystem":2},{"Timestamp":"2022-10-27T18:43:53.696+00:00","Latitude":41.5263819,"Longitude":-81.6507921,"Altitude":153.29998779296875,"Accuracy":20.0,"VerticalAccuracy":1.7697961330413818,"Speed":null,"Course":null,"IsFromMockProvider":false,"AltitudeReferenceSystem":2},{"Timestamp":"2022-10-27T18:43:54.526+00:00","Latitude":41.5263819,"Longitude":-81.6507921,"Altitude":153.29998779296875,"Accuracy":20.0,"VerticalAccuracy":1.7697961330413818,"Speed":null,"Course":null,"IsFromMockProvider":false,"AltitudeReferenceSystem":2},{"Timestamp":"2022-10-27T18:43:55.374+00:00","Latitude":41.5263819,"Longitude":-81.6507921,"Altitude":153.29998779296875,"Accuracy":20.0,"VerticalAccuracy":1.7697961330413818,"Speed":null,"Course":null,"IsFromMockProvider":false,"AltitudeReferenceSystem":2},{"Timestamp":"2022-10-27T18:43:56.21+00:00","Latitude":41.5263811,"Longitude":-81.650792,"Altitude":153.29998779296875,"Accuracy":20.0,"VerticalAccuracy":1.7160584926605225,"Speed":null,"Course":null,"IsFromMockProvider":false,"AltitudeReferenceSystem":2}]
Every subsequent time I hit start/stop on the app, I get the real-time data in the output multiplied by the number of times I've started/stopped since the last compiling.
the main app page button event thats fired:
private async void GPSToggle_Clicked(object sender, EventArgs e)
{
var LocationPermissionStatus = await Xamarin.Essentials.Permissions.RequestAsync<Xamarin.Essentials.Permissions.LocationAlways>();
var FileReadPermissionStatus = await Xamarin.Essentials.Permissions.RequestAsync<Xamarin.Essentials.Permissions.StorageRead>();
var FileWritePermissionStatus = await Xamarin.Essentials.Permissions.RequestAsync<Xamarin.Essentials.Permissions.StorageWrite>();
if(LocationPermissionStatus == Xamarin.Essentials.PermissionStatus.Denied)
{
// TODO
return;
}
// run if device is android
if(Device.RuntimePlatform == Device.Android)
{
if (!CrossGeolocator.Current.IsGeolocationAvailable || !CrossGeolocator.Current.IsGeolocationEnabled)
{
// gps is not enabled, throw alert
Console.WriteLine("---------------------------------------------------------------GPS is DISABLED");
await DisplayAlert("Error", "GPS is not enabled. You must enable GPS to use this feature", "Ok");
}
else
{
// set our IsTracking = true flag
if (!IsTracking)
{
// start background listening for GPS
await StartListening();
Console.WriteLine("---------------------------------------------------------------Listening: " + CrossGeolocator.Current.IsListening);
StartService();
Console.WriteLine("---------------------------------------------------------------Service initiated");
IsTracking = true;
Console.WriteLine("---------------------------------------------------------------Tracking initiated");
GPSToggle.Text = "Stop Tracking";
}
else
{
//
// verify that the submittal wasn't done in error, before stopping services and submitting data
bool DoneInError = await DisplayAlert("Finish?", "Are you sure you want to stop services and submit?", "No", "Yes");
if (!DoneInError)
{
await StopListening();
Console.WriteLine("---------------------------------------------------------------listening:" + CrossGeolocator.Current.IsListening);
IsTracking = false;
Console.WriteLine("---------------------------------------------------------------Tracking ceased");
// stop the gps service
StopService();
Console.WriteLine("---------------------------------------------------------------Service ceased");
// stop the background listening for gps
Console.WriteLine("---------------------------------------------------------------Attempt GPX parse from buffer obj");
GPSToggle.Text = "Start Tracking";
}
}
}
}
}
Specifically the line:
StartService();
Fires this method off within the same class, specifically the MessagingCenter.Send<>, which initiates my foreground service to handle logging the gps data into a buffer:
private void StartService()
{
var startServiceMessage = new StartServiceMessage();
MessagingCenter.Send(startServiceMessage, "ServiceStarted");
Preferences.Set("LocationServiceRunning", true);
StatusLabel.Text = "Location service has been started";
Console.WriteLine("---------------------------------------------------------------location service has been started. preferences saved");
}
and
StopService();
Fires this method off to stop the services and retrieve the gps buffer data from the foreground to the main thread:
private void StopService()
{
var stopServiceMessage = new StopServiceMessage();
MessagingCenter.Unsubscribe<App, List<Location>>(this, "GPXBufferData");
MessagingCenter.Subscribe<App, List<Location>>(this, "GPXBufferData", (sender, args) =>
{
RawGPXData = args;
Generate_CreateGPX_File(RawGPXData);
RawBufferToJsonString = GPXParse.GenerateJSON_GPXPoints(RawGPXData);
Console.WriteLine("---------------------------------------------------------------:RawBufferToJsonString: " + RawBufferToJsonString);
PromptForSubmission_GPXPoints_API();
});
Console.WriteLine("--------------------------------------------------------------------------");
MessagingCenter.Send(stopServiceMessage, "ServiceStopped");
Preferences.Set("LocationServiceRunning", false);
Console.WriteLine("---------------------------------------------------------------Location service stopped. preferences saved");
}
In the above snippet, this line is subscribed to in the GPSLocationService.cs file:
MessagingCenter.Send(stopServiceMessage, "ServiceStopped");
This is a portion of my GPSLocationService.cs file that is relevant to this:
public async Task Run(CancellationToken token)
{
int ObjCount = 0;
await Task.Run(async () => {
// if the task was stopped
// check the buffer for data, if data, send to GPXGenerator
MessagingCenter.Subscribe<StopServiceMessage>(this, "ServiceStopped",
message =>
{
if (GPSBufferObj != null)
{
Device.BeginInvokeOnMainThread(() =>
{
MessagingCenter.Unsubscribe<App, List<Location>>((App)Xamarin.Forms.Application.Current, "GPXBufferData");
MessagingCenter.Send<App, List<Location>>((App)Xamarin.Forms.Application.Current, "GPXBufferData", GPSBufferObj);
});
}
});
return;
}, token);
}
I believe I have tracked down where the issue is starting. In my StopService() method, I have the following line (just to keep track of where Im at in the buffer) and it is only sent to output once.
Console.WriteLine("--------------------------------------------------------------------------");
BUT if I place that same line within the pasted portion of my GPSLocationService.cs file, I will get the incremented output. I'm leaning towards the nested task being the issue, I wrote this based losely off of this example repro:
https://github.com/jfversluis/XFBackgroundLocationSample
You don't have MessagingCenter.Unsubscribe<StopServiceMessage> anywhere in your code. StopServiceMessage is what you are accumulating subscriptions to.
You need to make sure Unsubscribe is unsubscribing the instance that was previously subscribed to. It sounds to me like there are multiple instances of GPSLocationService. [In which case, this is no longer referring to the original instance. Unsubscribe won't do anything, unless you have the this that was originally Subscribed.]
If so, instead create an instance of GPSLocationService ONCE, and store it in a static variable. Re-use it. start/stop it, but don't discard it.
Alternatively, if you only want a message ONE TIME from each Subscribe, then Unsubscribe as soon as you receive each message:
MessagingCenter.Subscribe<StopServiceMessage>(this, "ServiceStopped",
message =>
{
MessagingCenter.Unsubscribe<StopServiceMessage>(this, "ServiceStopped");
... your code ...
});
Use this same pattern EVERYWHERE you have a Subscribe (unless you Subscribe ONLY ONE TIME at app start, as Jason suggested.)
I'm new to C# .Net and Visual Studio 2022 - What I'm trying to achieve is to have a timer running every second to check that a website url is valid/is up. If the url IS reachable and the current WebView2 is not showing that website, then it should navigate to it. If it's already showing that website, it should do nothing else. If it was showing that website, but now it's no longer valid, the WebView should navigate to my custom error page. If whilst on the custom error page the website becomes available again, it should (re)load the website.
In my particular scenario I'm making a webView load localhost (127.0.0.1) for now. I want to continuously check the website is ip, and if it goes down, show custom error, if it comes back, show the website.
Not sure I'm explaining that very well. From the research I have done, I believe I need Task and also await using async method.
Here's my current timer and checkurl code as well as navigtionstarted and navigationcompeted:
private void webView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
timerCheckRSLCDURL.Enabled = false;
}
private void webView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
if (e.IsSuccess)
{
Debug.WriteLine("JT:IsSuccess");
((Microsoft.Web.WebView2.WinForms.WebView2) sender).ExecuteScriptAsync("document.querySelector('body').style.overflow='hidden'");
}
else if (!e.IsSuccess)
{
Debug.WriteLine("JT:IsNOTSuccess");
webView.DefaultBackgroundColor = Color.Blue;
//webView.CoreWebView2.NavigateToString(Program.htmlString);
}
timerCheckRSLCDURL.Enabled = true;
}
private void timerCheckRSLCDURL_Tick(object sender, EventArgs e)
{
Debug.WriteLine("Timer Fired! Timer.Enabled = " + timerCheckRSLCDURL.Enabled);
CheckURL(Properties.Settings.Default.URL, Properties.Settings.Default.Port);
}
private async void CheckURL(string url, decimal port)
{
timerCheckRSLCDURL = false;
Program.isWebSiteUp = false;
string webViewURL = BuildURL();
Debug.WriteLine("Checking URL: " + webViewURL);
try
{
var request = WebRequest.Create(webViewURL);
request.Method = "HEAD";
var response = (HttpWebResponse) await Task.Factory.FromAsync < WebResponse > (request.BeginGetResponse, request.EndGetResponse, null);
if (response.StatusCode == HttpStatusCode.OK)
{
Program.isWebSiteUp = true;
}
}
catch (System.Net.WebException exception)
{
Debug.WriteLine("WebException: " + exception.Message);
if (exception.Message.Contains("(401) Unauthorized"))
{
Program.isWebSiteUp = false;
}
else
{
Program.isWebSiteUp = false;
} // This little block is unfinished atm as it doesn't really affect me right now
}
catch (Exception exception)
{
Debug.WriteLine("Exception: " + exception.Message);
Program.isWebSiteUp = false;
}
if (Program.isWebSiteUp == true && webView.Source.ToString().Equals("about:blank"))
{
Debug.WriteLine("JT:1");
Debug.WriteLine("isWebSiteUp = true, webView.Source = about:blank");
webView.CoreWebView2.Navigate(webViewURL);
}
else if (Program.isWebSiteUp == true && !webView.Source.ToString().Equals(webViewURL))
{
Debug.WriteLine("JT:2");
Debug.WriteLine("isWebSiteUp = true\nwebView.Source = " + webView.Source.ToString() + "\nwebViewURL = " + webViewURL + "\nWebView Source == webViewURL: " + webView.Source.ToString().Equals(webViewURL) + "\n");
webView.CoreWebView2.Navigate(webViewURL);
}
else if (Program.isWebSiteUp == false && !webView.Source.ToString().Equals("about:blank"))
{
Debug.WriteLine("JT:3");
Debug.WriteLine("This SHOULD be reloading the BSOD page!");
webView.CoreWebView2.NavigateToString(Program.htmlString);
}
}
private string BuildURL()
{
string webViewURL;
string stringURL = Properties.Settings.Default.URL;
string stringPort = Properties.Settings.Default.Port.ToString();
string stringURLPORT = $ "{stringURL}:{stringPort}";
if (stringPort.Equals("80"))
{
webViewURL = stringURL;
}
else
{
webViewURL = stringURLPORT;
}
if (!webViewURL.EndsWith("/"))
{
webViewURL += "/";
}
//For now, the URL will always be at root, so don't need to worry about accidentally
//making an invalid url like http://example.com/subfolder/:port
//although potentially will need to address this at a later stage
Debug.WriteLine("BuildURL returns: " + webViewURL);
return webViewURL;
}
So the timer is fired every 1000ms (1 second) because I need to actively check the URL is still alive. I think the way I'm controlling the timer is wrong - and I imagine there's a better way of doing it, but what I want to do is this...
Check website URL every 1 second
To avoid repeating the same async task, I'm trying to disable the timer so it does not fire a second time whilst the async checkurl is running
Once the async/await task of checking the url has finished, the timer should be re-enabled to continue monitoring is the website url is still up
If the website is down, it should show my custom error page (referred to as BSOD) which is some super basic html loaded from resources and 'stored' in Program.htmlString
if the the website is down, and the webview is already showing the BSOD, the webview should do nothing. The timer should continue to monitor the URL.
if the website is up and the webview is showing the BSOD, then it should navigate to the checked url that is up. If the website is up, and the webview is already showing the website, then the webview should do nothing. The timer should continue to monitor the URL.
From other research, I'm aware I shouldn't be using private async void - eg shouldn't be using it as a void. But I've not yet figured out / understood the correct way to do this
In the Immediate Window, it appears that webView_NavigationCompleted is being fired twice (or sometimes even a few times) instantly as the immediate window output will show JT:IsSuccess or JT:IsNOTSuccess a few times repeated in quick succession. Is that normal? I'm assuming something isn't correct there.
The main problem appears to be due to the timer being only 1 second. If I change the timer to fire every 30 seconds for example, it seems to work ok, but when it's every second (I may even need it less than that at some point) it's not really working as expected. Sometimes the BSOD doesn't load at all for example, as well as the webView_NavigationCompleted being fire multiple times in quick succession etc.
Could someone pretty please help me make this code better and correct.
I've searched countless websites etc and whilst there is some good info, some of it seems overwhelming / too technical so to speak. I had to lookup what "antecedent" meant earlier as it's a completely new word to me! :facepalm:
Many thanks inadvance
This answer will focus on the Task timer loop to answer the specific part of your question "check a url is valid every second". There are lots of answers about how to perform the actual Ping (like How do you check if a website is online in C#) and here's the Microsoft documentation for Ping if you choose to go that route.
Since it's not uncommon to set a timeout value of 120 seconds for a ping request, it calls into question whether it would have any value to do this on a steady tick of one second. My suggestion is that it would make more sense to:
Make a background thread
Perform a synchronous ping (wait for the result) on the background thread.
Marshal the ping result onto the UI thread to perform the other tasks you have laid out.
Synchronously wait a Task.Delay on the background thread before performing the next ping.
Here is how I personally go about doing that in my own production code:
void execPing()
{
Task.Run(() =>
{
while (!DisposePing.IsCancellationRequested)
{
var pingSender = new Ping();
var pingOptions = new PingOptions
{
DontFragment = true,
};
// https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ping?view=net-6.0#examples
// Create a buffer of 32 bytes of data to be transmitted.
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
int timeout = 120;
try
{
// https://stackoverflow.com/a/25654227/5438626
if (Uri.TryCreate(textBoxUri.Text, UriKind.Absolute, out Uri? uri)
&& (uri.Scheme == Uri.UriSchemeHttp ||
uri.Scheme == Uri.UriSchemeHttps))
{
PingReply reply = pingSender.Send(
uri.Host,
timeout, buffer,
pingOptions);
switch (reply.Status)
{
case IPStatus.Success:
Invoke(() => onPingSuccess());
break;
default:
Invoke(() => onPingFailed(reply.Status));
break;
}
}
else
{
Invoke(() => labelStatus.Text =
$"{DateTime.Now}: Invalid URI: try 'http://");
}
}
catch (Exception ex)
{
// https://stackoverflow.com/a/60827505/5438626
if (ex.InnerException == null)
{
Invoke(() => labelStatus.Text = ex.Message);
}
else
{
Invoke(() => labelStatus.Text = ex.InnerException.Message);
}
}
Task.Delay(1000).Wait();
}
});
}
What works for me is initializing it when the main window handle is created:
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (!(DesignMode || _isHandleInitialized))
{
_isHandleInitialized = true;
execPing();
}
}
bool _isHandleInitialized = false;
Where:
private void onPingSuccess()
{
labelStatus.Text = $"{DateTime.Now}: {IPStatus.Success}";
// Up to you what you do here
}
private void onPingFailed(IPStatus status)
{
labelStatus.Text = $"{DateTime.Now}: {status}";
// Up to you what you do here
}
public CancellationTokenSource DisposePing { get; } = new CancellationTokenSource();
Example 404:
Needless to say I'm new to C# and doing some basic things to learn along the way. I'm trying to understand async/await feature. I have a form with 2 textboxes and a button. When I click the button, it checks a remote computer's service status (in this case remote registry. While it checks, it writes that it is checking the registry and tells the user to wait. Then starts if the state is stopped and gets some values I look for and writes those to the second textbox.
My code works, and I'm able to get my values. But the GUI freezes during this time. I tried to implement backgroundworker, thread and Async/Wait with no success. I tried doing Task and couldn't get that to work.
All the examples shown here or on other sites simply return int while I'm trying to return a string that writes the status and the values I want to textboxes. I tried to convert it to string with no success.
Long story short, for this type of processes, what would be a better approach? Can someone show me how and comment what does what along the way just so I understand better? I want to learn how to do it but at the same time learn why and learn to write cleaner code.
Thank you everyone for taking their time in advance.
string ComputerName = "Lab01";
public frmRegChecker()
{
InitializeComponent();
}
private void Button1_Click(object sender, EventArgs e)
{
Check_Status();
}
private void Check_Status()
{
TxtBoxstatus.AppendText("Checking Remote Registry service status on computer : " + ComputerName);
TxtBoxstatus.AppendText(Environment.NewLine);
TxtBoxstatus.AppendText("Please wait... ");
ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
try
{
TxtBoxstatus.AppendText("The Remote Registry service status is currently set to : " + sc.Status.ToString());
TxtBoxstatus.AppendText(Environment.NewLine);
if (sc.Status == ServiceControllerStatus.Stopped)
{
// Start the service if the current status is stopped.
TxtBoxstatus.AppendText("Starting Remote Registry service...");
TxtBoxstatus.AppendText(Environment.NewLine);
try
{
// Start the service, and wait until its status is "Running".
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
sc.Refresh();
sc.WaitForStatus(ServiceControllerStatus.Running);
// Display the current service status.
TxtBoxstatus.AppendText("The Remote Registry status is now set to:" + sc.Status.ToString());
richTextBox1.AppendText(Environment.NewLine);
try
{
var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
var key = reg.OpenSubKey(#"Software\Microsoft\Windows NT\CurrentVersion\");
string _OSVersion = (key.GetValue("CurrentVersion")).ToString();
richTextBox1.AppendText("OS version is : " + _OSVersion);
richTextBox1.AppendText(Environment.NewLine);
}
catch (InvalidOperationException)
{
richTextBox1.AppendText("Error getting registry value from" + ComputerName);
richTextBox1.AppendText(Environment.NewLine);
}
}
catch (InvalidOperationException)
{
richTextBox1.AppendText("Could not start the Remote Registry service.");
richTextBox1.AppendText(Environment.NewLine);
}
}
else if (sc.Status == ServiceControllerStatus.Running)
{
try
{
var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
var key = reg.OpenSubKey(#"Software\Microsoft\Windows NT\CurrentVersion\");
string _OSVersion = (key.GetValue("CurrentVersion")).ToString();
richTextBox1.AppendText("OS version is : " + _OSVersion);
richTextBox1.AppendText(Environment.NewLine);
}
catch (InvalidOperationException)
{
richTextBox1.AppendText("Error getting registry value from" + ComputerName);
richTextBox1.AppendText(Environment.NewLine);
}
}
}
}
catch
{
richTextBox1.AppendText("Error getting registry value from " + ComputerName);
richTextBox1.AppendText(Environment.NewLine);
}
}
Unfortunately, ServiceController is a rather outdated class and doesn't natively support async/await. That would be the cleanest solution if it were possible.
So, what you can do is use async/await with Task.Run. Task.Run executes code on a background thread, and you can use async/await from the UI to consume that background operation. This approach allows exceptions to be propagated and handled in a natural fashion, and it allows return values to also be handled naturally. Strip out the textbox updates for now for simplicity, and the first step looks like this:
private async void Button1_Click(object sender, EventArgs e)
{
try
{
var osVersion = await Task.Run(() => CheckStatus());
richTextBox1.AppendText("OS version is : " + osVersion);
richTextBox1.AppendText(Environment.NewLine);
}
catch (InvalidOperationException ex)
{
richTextBox1.AppendText("Error getting registry value from" + ComputerName);
richTextBox1.AppendText(ex.ToString());
richTextBox1.AppendText(Environment.NewLine);
}
}
private string CheckStatus()
{
ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
if (sc.Status == ServiceControllerStatus.Stopped)
{
// Start the service if the current status is stopped.
// Start the service, and wait until its status is "Running".
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
sc.Refresh();
sc.WaitForStatus(ServiceControllerStatus.Running);
}
var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
var key = reg.OpenSubKey(#"Software\Microsoft\Windows NT\CurrentVersion\");
return (key.GetValue("CurrentVersion")).ToString();
}
Next, add the progress updates. There's a built-in mechanism for that - IProgress<T>/Progress<T>, and it works like this:
private async void Button1_Click(object sender, EventArgs e)
{
try
{
var progress = new Progress<string>(update =>
{
TxtBoxstatus.AppendText(update);
TxtBoxstatus.AppendText(Environment.NewLine);
});
var osVersion = await Task.Run(() => CheckStatus(progress));
richTextBox1.AppendText("OS version is : " + osVersion);
richTextBox1.AppendText(Environment.NewLine);
}
catch (InvalidOperationException ex)
{
richTextBox1.AppendText("Error getting registry value from" + ComputerName);
richTextBox1.AppendText(ex.ToString());
richTextBox1.AppendText(Environment.NewLine);
}
}
private string CheckStatus(IProgres<string> progress)
{
progress?.Report("Checking Remote Registry service status on computer : " + ComputerName);
progress?.Report("Please wait... ");
ServiceController sc = new ServiceController("RemoteRegistry", ComputerName);
progress?.Report("The Remote Registry service status is currently set to : " + sc.Status.ToString());
if (sc.Status == ServiceControllerStatus.Stopped)
{
// Start the service if the current status is stopped.
progress?.Report("Starting Remote Registry service...");
// Start the service, and wait until its status is "Running".
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 3));
sc.Refresh();
sc.WaitForStatus(ServiceControllerStatus.Running);
progress?.Report("The Remote Registry status is now set to:" + sc.Status.ToString());
}
var reg = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ComputerName);
var key = reg.OpenSubKey(#"Software\Microsoft\Windows NT\CurrentVersion\");
return (key.GetValue("CurrentVersion")).ToString();
}
Note the separation of concerns: the only code that touches UI objects is a UI event handler. The CheckStatus method that contains the actual program logic is separated from the UI - all it knows is that it can report progress strings and return a string result.
This can easily be accomplished using async/await. An Example:
A simple WPF form:
<Window x:Class="WpfApp1.MainWindow"
...>
<Grid>
<Button Name="button" Content="Start" Click="Button_Click"/>
<TextBox Name="textButton" />
</Grid>
</Window>
and the associated code-behind:
public partial class MainWindow:Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
textButton.Text = "Running...";
string result = await DoIt();
textButton.Text = result;
}
private async Task<string> DoIt()
{
await Task.Delay(3000);
return "Finished...";
}
}
When clicking the button, the long-running 'calculation' in 'DoIt' is started'asynchrounously. While it is running, the UI remains responsive.
When DoIt returns returns its result (in this example after a dummy delay of 3 seconds) the 'click' eventhandler continues...
Remarks:
this example uses code behind for simplicity. The technique works as well using the MVVM-pattern (where the async operation is started from a command).
'async void' is an anti pattern, but is used to comply withe the auto generated eventhandler in code behind.
in a real world application the async 'DoIt' method would propably be situated in a backend 'model class'.
the example assumes you only want to update the UI once the long-running operation has finished. If you want intermediate updates, there are several options (unfortunately a bit more complex).
I think you can still use the backgroundworker to make your textboxes display messages without freezing the GUI. Take reference from this question Here on how to return an object from backgroundworker.
I have the following WCF Data Service code in a Xamarin Forms application.
It updates a simple row in a table.
Static.Dialogs.Alert("Starting");
DataServiceQuery<SRef.SimpleObject> query = (DataServiceQuery<SRef.SimpleObject>)Entities.SimpleObject.Where(x => x.ID == Guid.Parse("DEF47A0F-AF1E-4043-B8C8-56084841E80B"));
query.BeginExecute((result) =>
{
try
{
Static.Dialogs.Alert("Getting the object");
var actData = query.EndExecute(result).FirstOrDefault();
if (actData != null)
{
actData.Info = "Info"+randomNumber;
Entities.UpdateObject(actData);
Entities.ChangeState(actData, EntityStates.Modified);
Static.Dialogs.Alert("Before the update");
Entities.BeginSaveChanges(SaveChangesOptions.BatchWithIndependentOperations, (result2) =>
{
try
{
Static.Dialogs.Alert("BeginSaveChanges starts");
var r = Entities.EndSaveChanges(result2);
Static.Dialogs.Alert("Update done ");
}
catch (Exception ex2)
{
Static.Dialogs.Alert("Error:" + ex2.Message);
}
}, null);
}
else
Static.Dialogs.Alert("No object");
}
catch (Exception ex1)
{
Static.Dialogs.Alert("Error:" + ex1.Message);
}
}, null);
}
catch (Exception ex)
{
Static.Dialogs.Alert("Error:" + ex.Message);
}
});
I have tested it on an emulator and a physical device.
Sometimes it works perfectly.
Sometimes I get only these messages:
Starting
Getting the object
Before the update
Sometimes only these:
Starting
It mostly gets wrong when I get a perfect update and I try again. Like it has 'used up' the only connection and after that it doesn't work.
On the server side I log every error and nothing is catched there. Also, no exception on the client side.
The DataServiceContext MergeOption is set to PreserveChanges.
What could affect it? When I send out a request, I have to wait some time? Should I close the connection somehow?
I reckon it is some kind of cache problem.
UPDATE:
I tried again, with a simpler approach (I only save a new item now):
private DataServiceReference.DataEntities entities;
public DataServiceReference.DataEntities Entities
{
get
{
if (entities == null)
{
entities = new DataServiceReference.DataEntities(Static.BaseURI);
entities.MergeOption = MergeOption.OverwriteChanges;
entities.SaveChangesDefaultOptions = SaveChangesOptions.ReplaceOnUpdate;
}
return entities;
}
}
var newItem = new DataServiceReference.Info()
{
Name = "Name " + DateTime.Now.Second,
ID = Guid.NewGuid(),
Role = "1"
};
Entities.AddToInfo(newItem);
try
{
foreach (var item in Entities.Entities)
{
System.Diagnostics.Debug.WriteLine(item.Identity + " " + item.State);
}
var res = Entities.BeginSaveChanges(SaveChangesOptions.Batch,
(result) =>
{
//var s = 3; //debug point - only hit once
try
{
//back to the UI thread
Xamarin.Forms.Device.BeginInvokeOnMainThread(() =>
//(result.AsyncState as DataServiceReference.DataEntities).EndSaveChanges(result));
Entities.EndSaveChanges(result));
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
throw;
}
}, Entities);
//res.AsyncWaitHandle.WaitOne(1000); //it only blocks the main thread, no use
resultList.Add(newItem.Name);
}
catch (Exception ex2)
{
System.Diagnostics.Debug.WriteLine(ex2.Message);
throw;
}
I also read (and it was pointed out in the first answer) that the result is provided on a different thread, so I added a dispatcher call to get the results (note the UI thread call: Xamarin.Forms.Device.BeginInvokeOnMainThread).
In an application where the callback must be invoked on a specific
thread, you must explicitly marshal the execution of the End method,
which handles the response, to the desired thread. For example, in
Windows Presentation Foundation (WPF)-based applications and
Silverlight-based applications, the response must be marshaled back to
the UI thread by using the BeginInvoke method on the Dispatcher
object.
Note the mentioning of the End method!
I added the following debug message:
foreach (var item in Entities.Entities)
{
System.Diagnostics.Debug.WriteLine(item.Identity + " " + item.State);
}
The result:
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'f1057131-90ee-11d7-9812-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'f1057133-90ee-11d7-9812-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'f6cfce91-90ef-11d7-9812-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'a6c2d822-91a7-11d7-9813-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'a6c2d823-91a7-11d7-9813-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'a6c2d824-91a7-11d7-9813-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'b750e561-91b8-11d7-9813-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'b750e562-91b8-11d7-9813-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'b750e563-91b8-11d7-9813-0040f6cc1384') Unchanged
[0:]http://192.168.1.100/InfoDataService/InfoDataService.svc/Info(guid'eee2d1f7-17cb-4283-a053-01f6cf7bb2fd') Unchanged
[0:] Added
[0:] Added
[0:] Added
It seems, that the context keeps gathering the objects but it sends only the first new object to the service and the other objects keep piling up.
The article emphasizes the thread issues of the End method, but the callback is the real issue here (it is not fired), so we never get to the End call and the UI thread.
This seems like a serious bug and I can't tell what to do...
The reason is that BeginExecute will start another thread, and your main thread returned even when the sub-thread doesn't finished. Then this code may even not execute the BeginSaveChanges.
So to make sure all the sub threads finished. The main thread need to wait for the sub threads.
The simplest way is to System.Threading.Thread.Sleep(5000); Or you can use IAsyncResult.AsyncWaitHandle.WaitOne()
I have created a listbox where I am adding items inside a loop. But I am only able to see the item all together after the loop finishes. Whether it is a running application and it is adding some data to the DB which I need to show after adding each data.
Below is my code:
public void checkAutoRenewalWeekly(string keyword, string lang, int price, int provid2)
{
try
{
//here I do something
for (i = 0; i <= ds.Tables[0].Rows.Count - 1; i++)
{
//Here I do something else and then add it to the list box
logstr = DateTime.Now.ToString("HH:mm:ss") + " Renew " + keyword +
" : " + pmsisdn.Trim() + " ";
// Here I am adding items but can only see after completing
// the whole functionality as this method is being called from
// another method so after completing that function
// I can see the items all together
this.lsLog.Items.Insert(0, logstr);
lsLog.Show();
}
ds.Clear();
}
catch (Exception ex)
{
logstr = DateTime.Now.ToString("HH:mm:ss") + ex.Message;
this.lsLog.Items.Insert(0, logstr);
lsLog.Show();
process_log(logstr, "err");
logstr = ex.StackTrace;
process_log(logstr, "err");
}
}
Even I can't see the catch block writing also unless the method completes everything it is doing. Please help me on this. Thank you.
lsLog.Show() doesn't do what you think it does, you need to release the UI thread for long enough to redraw the screen, which in a Windows Forms app, you can do by calling Application.DoEvents()