I'm working on WinRT. If an unhandled exception is thrown I want to write the message text to the storage.
I added an Event handler in 'App.xaml.cs', see the code.
The exception is caught but the last line, where the file is written, crashes again -> 'exception'!
Why? Any idea?
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
this.UnhandledException += App_UnhandledException;
}
async void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder;
StorageFile file= await folder.CreateFileAsync("crash.log",CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, e.Message); // <----- crash again -----
}
Thanks
Sunny
I've been wondering the same thing and stumbled across this quite early on in my search. I've figured out a way, hopefully this will prove useful to someone else too.
The problem is that await is returning control of the UI thread and the app's crashing. You need a deferral but there's no real way to get one.
My solution is to use the settings storage, instead. I'm assuming most people wanting to do this want to do something LittleWatson style, so here's some code modified from http://blogs.msdn.com/b/andypennell/archive/2010/11/01/error-reporting-on-windows-phone-7.aspx for your convenience:
namespace YourApp
{
using Windows.Storage;
using Windows.UI.Popups;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
public class LittleWatson
{
private const string settingname = "LittleWatsonDetails";
private const string email = "mailto:?to=you#example.com&subject=YourApp auto-generated problem report&body=";
private const string extra = "extra", message = "message", stacktrace = "stacktrace";
internal static void ReportException(Exception ex, string extraData)
{
ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always);
var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values;
exceptionValues[extra] = extraData;
exceptionValues[message] = ex.Message;
exceptionValues[stacktrace] = ex.StackTrace;
}
internal async static Task CheckForPreviousException()
{
var container = ApplicationData.Current.LocalSettings.Containers;
try
{
var exceptionValues = container[settingname].Values;
string extraData = exceptionValues[extra] as string;
string messageData = exceptionValues[message] as string;
string stacktraceData = exceptionValues[stacktrace] as string;
var sb = new StringBuilder();
sb.AppendLine(extraData);
sb.AppendLine(messageData);
sb.AppendLine(stacktraceData);
string contents = sb.ToString();
SafeDeleteLog();
if (stacktraceData != null && stacktraceData.Length > 0)
{
var dialog = new MessageDialog("A problem occured the last time you ran this application. Would you like to report it so that we can fix the error?", "Error Report")
{
CancelCommandIndex = 1,
DefaultCommandIndex = 0
};
dialog.Commands.Add(new UICommand("Send", async delegate
{
var mailToSend = email.ToString();
mailToSend += contents;
var mailto = new Uri(mailToSend);
await Windows.System.Launcher.LaunchUriAsync(mailto);
}));
dialog.Commands.Add(new UICommand("Cancel"));
await dialog.ShowAsync();
}
}
catch (KeyNotFoundException)
{
// KeyNotFoundException will fire if we've not ever had crash data. No worries!
}
}
private static void SafeDeleteLog()
{
ApplicationData.Current.LocalSettings.CreateContainer(settingname, Windows.Storage.ApplicationDataCreateDisposition.Always);
var exceptionValues = ApplicationData.Current.LocalSettings.Containers[settingname].Values;
exceptionValues[extra] = string.Empty;
exceptionValues[message] = string.Empty;
exceptionValues[stacktrace] = string.Empty;
}
}
}
To implement it, you need to do the same as the link above says, but to ensure the data's here in case the url ever goes down:
App.xaml.cs Constructor (BEFORE the call to this.InitializeComponent()):
this.UnhandledException += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");
Obviously if you already have an UnhandledException method you can throw the call to LittleWatson in there.
If you're on Windows 8.1, you can add a NavigationFailed call too. This needs to be in an actual page (typically MainPage.xaml.cs or whatever page is first opened):
xx.xaml.cs Constructor (any given page):
rootFrame.NavigationFailed += (s, e) => LittleWatson.ReportException(e.Exception, "extra message goes here");
Lastly, you need to ask the user if they want to send the e-mail when the app re-opens. In your app's default Page's constructor (default: the page App.xaml.cs initializes):
this.Loaded += async (s, e) => await LittleWatson.CheckForPreviousException();
Or add the call to your OnLoad method if you already use it.
In this situation, await could be loosely translated to "do this job on another thread, and continue what you were doing while you wait for it to finish". Given that what your app was doing was crashing, you probably don't want it to continue doing that until you're done logging the problem. I'd suggest running your file IO synchronously in this case.
This may come a bit too late for the original question but...
as #Hans Passant suggested, avoiding await (i.e., running the FileIO.AppendTextAsync() synchronously), also seconded by #Jon, I would opt for this rather than the relatively too heavy code for LittleWatson. As the app is in some error handing state anyway (this should be a rare occurrence) I wouldn't put any blocking arising from synchronous (due to removing await) as a major downside.
Leaving the synchronous option to one side, the following await implementation worked for me:
Change await FileIO.AppendTextAsync(file, e.Message); to:
Task task = LogErrorMessage(file, e.Message)
task.Wait(2000); // adjust the ms value as appropriate
...
private async Task LogErrorMessage(StorageFile file, string errorMessage)
{
await FileIO.AppendTextAsync(file, errorMessage); // this shouldn't crash in App_UnhandledException as it did before
}
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.)
In my UWP App I need to continuously send data to a UWP app from a WinForms (Win32) component and vice versa. However, I have a weird bug in my WinForms component. Sometimes, upon launching the WinForm, I get a System.InvalidOperationException when calling await connection.SendMessageAsync(message) saying: A method was called at an unexpected time Other times, it works perfectly.
My Code:
private async void SendToUWPVoidAsync(object content)
{
ValueSet message = new ValueSet();
if (content != "request") message.Add("content", content);
else message.Add(content as string, "");
#region SendToUWP
// if connection isn't inited
if (connection == null)
{
// init
connection = new AppServiceConnection();
connection.PackageFamilyName = Package.Current.Id.FamilyName;
connection.AppServiceName = "NotifyIconsUWP";
connection.ServiceClosed += Connection_ServiceClosed;
// attempt connection
AppServiceConnectionStatus connectionStatus = await connection.OpenAsync();
}
AppServiceResponse serviceResponse = await connection.SendMessageAsync(message);
// get response
if (serviceResponse.Message.ContainsKey("content"))
{
object newMessage = null;
serviceResponse.Message.TryGetValue("content", out newMessage);
// if message is an int[]
if (newMessage is int[])
{
// init field vars
int indexInArray = 0;
foreach (int trueorfalse in (int[])newMessage)
{
// set bool state based on index
switch (indexInArray)
{
case 0:
notifyIcon1.Visible = Convert.ToBoolean(trueorfalse);
break;
case 1:
notifyIcon2.Visible = Convert.ToBoolean(trueorfalse);
break;
case 2:
notifyIcon3.Visible = Convert.ToBoolean(trueorfalse);
break;
default:
break;
}
indexInArray++;
}
}
}
#endregion
}
The method is called like this:
private void TCheckLockedKeys_Tick(object sender, EventArgs e)
{
...
if (statusesChanged)
{
// update all bools
bool1 = ...;
bool2 = ...;
bool3 = ...;
// build int[] from bool values
int[] statuses = new int[] { Convert.ToInt32(bool1), Convert.ToInt32(bool2), Convert.ToInt32(bool3) };
// update UWP sibling
SendToUWPVoidAsync(statuses);
}
// ask for new settings
SendToUWPVoidAsync("request");
}
TCheckLockedKeys_Tick.Interval is set to 250 milliseconds.
Is there any way to prevent or to correctly handle this exception without the WinForm Component exiting but still establishing the vital communication path?
Any ideas?
Thanks
Okay, I have found a solution. One might actually call it a workaround.
In my WinForm, I changed the code as follows:
AppServiceResponse serviceResponse = await connection.SendMessageAsync(message);
to:
AppServiceResponse serviceResponse = null;
try
{
// send message
serviceResponse = await connection.SendMessageAsync(message);
}
catch (Exception)
{
// exit
capsLockStatusNI.Visible = false;
numLockStatusNI.Visible = false;
scrollLockStatusNI.Visible = false;
Application.Exit();
}
I have also changed code in my App.xaml.cs file:
private async void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
if (this.appServiceDeferral != null)
{
// Complete the service deferral.
this.appServiceDeferral.Complete();
}
}
to:
private async void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
if (reason == BackgroundTaskCancellationReason.SystemPolicy)
{
// WinForm called Application.Exit()
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
}
if (this.appServiceDeferral != null)
{
// Complete the service deferral.
this.appServiceDeferral.Complete();
}
}
I know all I'm doing is, technically, relaunching the Form till it succeeds which is not entirely the correct way of solving it. But, it works.
Some advice based on
Reference Async/Await - Best Practices in Asynchronous Programming
Avoid using async void except with event handlers. Prefer async Task methods over async void methods.
async void methods a fire an forget, which can cause the issues being encountered as exceptions are not being thrown in the correct context.
Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started.
Given that the method is being called within an event handler. then refactor the method to use an async Task
private async Task SendToUWPVoidAsync(object content) {
//...
}
and update the event handler to be async
private async void TCheckLockedKeys_Tick(object sender, EventArgs e) {
try {
//...
if (statusesChanged) {
// update all bools
bool1 = ...;
bool2 = ...;
bool3 = ...;
// build int[] from bool values
int[] statuses = new int[] { Convert.ToInt32(bool1), Convert.ToInt32(bool2), Convert.ToInt32(bool3) };
// update UWP sibling
await SendToUWPVoidAsync(statuses);
}
// ask for new settings
await SendToUWPVoidAsync("request");
}catch {
//...handle error appropriately
}
}
which should also allow for any exception to be caught as shown in the example above.
I have a delegate method to run a heavy process in my app (I must use MS Framework 3.5):
private delegate void delRunJob(string strBox, string strJob);
Execution:
private void run()
{
string strBox = "G4P";
string strJob = "Test";
delRunJob delegateRunJob = new delRunJob(runJobThread);
delegateRunJob.Invoke(strBox, strJob);
}
In some part of the method runJobThread
I call to an external program (SAP - Remote Function Calls) to retrieve data. The execution of that line can take 1-30 mins.
private void runJobThread(string strBox, string strJob)
{
// CODE ...
sapLocFunction.Call(); // When this line is running I cannot cancel the process
// CODE ...
}
I want to allow the user cancel whole process.
How can achieve this? I tried some methods; but I fall in the same point; when this specific line is running I cannot stop the process.
Instead of using the delegate mechanism you have to study the async and await mechanism. When you understand this mechanism you can move to cancellationtoken.
An example doing both things can be found here :
http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx
Well; I find out a complicated, but effective, way to solve my problem:
a.) I created a "Helper application" to show a notification icon when the process is running (To ensure to don't interfere with the normal execution of the main app):
private void callHelper(bool blnClose = false)
{
if (blnClose)
fw.processKill("SDM Helper");
else
Process.Start(fw.appGetPath + "SDM Helper.exe");
}
b.) I created a Thread that call only the heavy process line.
c.) While the Thread is alive I check for external file named "cancel" (The "Helper application" do that; when the user click an option to cancel the process the Helper create the file).
d.) If exists the file; dispose all objects and break the while cycle.
e.) The method sapLocFunction.Call() will raise an exception but I expect errors.
private void runJobThread(string strBox, string strJob)
{
// CODE ...
Thread thrSapCall = new Thread(() =>
{
try { sapLocFunction.Call(); }
catch { /* Do nothing */ }
});
thrSapCall.Start();
while (thrSapCall.IsAlive)
{
Thread.Sleep(1000);
try
{
if (fw.fileExists(fw.appGetPath + "\\cancel"))
{
sapLocFunction = null;
sapLocTable = null;
sapConn.Logoff();
sapConn = null;
canceled = true;
break;
}
}
finally { /* Do nothing */ }
}
thrSapCall = null;
// CODE ...
}
Works like a charm!
I think you would have to resort to the method described here. Read the post to see why this is a long way from ideal.
Perhaps this might work...
private void runJobThread(string strBox, string strJob, CancellationToken token)
{
Thread t = Thread.CurrentThread;
using (token.Register(t.Abort))
{
// CODE ...
sapLocFunction.Call(); // When this line is running I cannot cancel the process
// CODE ...
}
}
A bit of dnspy exposes a cancel method on nco3.0.
private readonly static Type RfcConnection = typeof(RfcSessionManager).Assembly.GetType("SAP.Middleware.Connector.RfcConnection");
private readonly static Func<RfcDestination, object> GetConnection = typeof(RfcSessionManager).GetMethod("GetConnection", BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(typeof(Func<RfcDestination, object>)) as Func<RfcDestination, object>;
private readonly static MethodInfo Cancel = RfcConnection.GetMethod("Cancel", BindingFlags.Instance | BindingFlags.NonPublic);
object connection = null;
var completed = true;
using (var task = Task.Run(() => { connection = GetConnection(destination); rfcFunction.Invoke(destination); }))
{
try
{
completed = task.Wait(TimeSpan.FromSeconds(invokeTimeout));
if (!completed)
Cancel.Invoke(connection, null);
task.Wait();
}
catch(AggregateException e)
{
if (e.InnerException is RfcCommunicationCanceledException && !completed)
throw new TimeoutException($"SAP FM {functionName} on {destination} did not respond in {timeout} seconds.");
throw;
}
}
I am writing a universal app primarily targeting Windows Phone using SQLite-net.
During the course of operation, the user is presented with an option to download multiple files. At the end of each file download, I need to mark the file in the db as completed. I am using BackgroundDownloader in order to download files - the WP8.0 app used Background Transfer Service and that worked great. Files can be huge (some 200+ mbs, user content) and i am not looking forward to wrapping the downloads in the HttpClient or WebClient.
However, it seems that the progress callback doesn't work with awaits unless I actually breakpoint in the method.
The following are listings from a sample app i quickly put together that demonstrates the behaviour:
Model:
public class Field
{
[PrimaryKey]
[AutoIncrement]
public int Id { get; set; }
public bool Done { get; set; }
}
MainPage codebehind (i am creating a db here only for the purposes of this example!):
private async void Button_Click(object sender, RoutedEventArgs e)
{
using (var db = new SQLiteConnection(Windows.Storage.ApplicationData.Current.LocalFolder.Path + "//Main.db"))
{
db.CreateTable<Field>();
db.Commit();
}
this.DbConnection = new SQLiteAsyncConnection(Windows.Storage.ApplicationData.Current.LocalFolder.Path + "//My.db");
var dl = new BackgroundDownloader();
dl.CostPolicy = BackgroundTransferCostPolicy.Always;
var transferUri = new Uri("http://192.168.1.4/hello.world", UriKind.Absolute);
var folder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(
"Content",
CreationCollisionOption.OpenIfExists);
var localFile = await folder.CreateFileAsync("cheesecakes.file", CreationCollisionOption.ReplaceExisting);
var d = dl.CreateDownload(transferUri, localFile);
d.Priority = BackgroundTransferPriority.High;
var progressCallback = new Progress<DownloadOperation>(this.DownloadProgress);
await d.StartAsync().AsTask(progressCallback);
}
private async void DownloadProgress(DownloadOperation download)
{
Debug.WriteLine("Callback");
if (download.Progress.Status == BackgroundTransferStatus.Completed)
{
var f = new Field();
f.Done = true;
await this.DbConnection.InsertAsync(f);
Debug.WriteLine("DONE");
}
}
If i breakpoint inside the DownloadProgress and then press F5 i get both Debug messages, and my db gets a new record.
However, if i just let the code execute, i never see "DONE" printed to me and neither is my db updated.
I tried wrapping the code in a new task:
await Task.Run(
async () =>
{
Debug.WriteLine("taskrun");
.... OTHER CODE FROM ABOVE...
});
But again, i only get to see 'taskrun' if i breakpoint in the callback.
UPDATE I actually think this is more related to checking the status. E.g. the statements outside of the check are executed, but only once, whereas anything inside the check is not executed.
Is there any way to force that callback to be invoked when the download is completed?
private async void DownloadProgress(DownloadOperation download)
{
Debug.WriteLine("Callback");
var value = download.Progress.BytesReceived * 100 download.Progress.TotalBytesToReceive;
new System.Threading.ManualResetEvent(false).WaitOne(1000);
if (download.Progress.Status == BackgroundTransferStatus.Completed )
{
var f = new Field();
f.Done = true;
await this.DbConnection.InsertAsync(f);
Debug.WriteLine("DONE");
}
}
I had this problem too, and I solved this by sleeping for 1000 ms, which worked really well for me.
Not sure what is causing this, but I was able to get the sample app to work reliably by manually checking the bytes to download as opposed to relying on the DownloadOperation.Progress.Status:
private async void DownloadProgress(DownloadOperation download)
{
Debug.WriteLine("Callback");
var value = download.Progress.BytesReceived * 100 / download.Progress.TotalBytesToReceive;
if (download.Progress.Status == BackgroundTransferStatus.Completed || value >= 100)
{
var f = new Field();
f.Done = true;
await this.DbConnection.InsertAsync(f);
Debug.WriteLine("DONE");
}
This gets me to 'DONE' every time.
While keeping in mind that:
I am using a blocking queue that waits for ever until something is added to it
I might get a FileSystemWatcher event twice
The updated code:
{
FileProcessingManager processingManager = new FileProcessingManager();
processingManager.RegisterProcessor(new ExcelFileProcessor());
processingManager.RegisterProcessor(new PdfFileProcessor());
processingManager.Completed += new ProcessingCompletedHandler(ProcessingCompletedHandler);
processingManager.Completed += new ProcessingCompletedHandler(LogFileStatus);
while (true)
{
try
{
var jobData = (JobData)fileMonitor.FileQueue.Dequeue();
if (jobData == null)
break;
_pool.WaitOne();
Application.Log(String.Format("{0}:{1}", DateTime.Now.ToString(CultureInfo.InvariantCulture), "Thread launched"));
Task.Factory.StartNew(() => processingManager.Process(jobData));
}
catch (Exception e)
{
Application.Log(String.Format("{0}:{1}", DateTime.Now.ToString(CultureInfo.InvariantCulture), e.Message));
}
}
}
What are are you suggestions on making the code multi-threaded while taking into consideration the possibility that two identical string paths may be added into the blocking queue? I have left the possibility that this might happen and in this case.. the file would be processed twice, the thing is that sometimes I get it twice, sometimes not, it is really awkward, if you have suggestions on this, please tell.
The null checking is for exiting the loop, I intentionally add a null from outside the threaded loop to determine it to stop.
For multi-threading this... I would probably add a "Completed" event to your FileProcessingManager and register for it. One argument of that event will be the "bool" return value you currently have. Then in that event handler, I would do the checking of the bool and re-queueing of the file. Note that you will have to keep a reference to the FileMonitorManager. So, I would have this ThreadProc method be in a class where you keep the FileMonitorManager and FileProcessingManager instances in a property.
To deduplicate, in ThreadProc, I would create a List outside of the while loop. Then inside the while loop, before you process a file, lock that list, check to see if the string is already in there, if not, add the string to the list and process the file, if it is, then skip processing.
Obviously, this is based on little information surrounding your method but my 2 cents anyway.
Rough code, from Notepad:
private static FileMonitorManager fileMon = null;
private static FileProcessingManager processingManager = new FileProcessingManager();
private static void ThreadProc(object param)
{
processingManager.RegisterProcessor(new ExcelFileProcessor());
processingManager.RegisterProcessor(new PdfFileProcessor());
processingManager.Completed += ProcessingCompletedHandler;
var procList = new List<string>();
while (true)
{
try
{
var path = (string)fileMon.FileQueue.Dequeue();
if (path == null)
break;
bool processThis = false;
lock(procList)
{
if(!procList.Contains(path))
{
processThis = true;
procList.Add(path);
}
}
if(processThis)
{
Thread t = new Thread (new ParameterizedThreadStart(processingManager.Process));
t.Start (path);
}
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
}
}
}
private static void ProcessingCompletedHandler(bool status, string path)
{
if (!status)
{
fileMon.FileQueue.Enqueue(path);
Console.WriteLine("\n\nError on file: " + path);
}
else
Console.WriteLine("\n\nSucces on file: " + path);
}