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.
Related
Here's a demo of code that is calling an "old library" which if successful, returns nothing, and if error, throws an exception.
public void ExampleCallOldCode()
{
List<string> users = new List<string>() {"user123", "user456"};
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
DoSomethingSynchronously(userId);
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void DoSomethingSynchronously(string userId)
{
if (userId.Contains("123"))
Console.WriteLine($"Doing something with {userId}");
else
throw new Exception("UserId needs to contain 123.");
}
We are now upgrading/integrating to a "new library" which performs the work asynchronously (which uses batching/queuing logic behind the scenese) and uses callbacks to notify of success or failure.
FYI: The "old library" is a wrapper that send emails. The new library is Segment's Analytics.Net package
I don't want to change too much of the old code (it is used in MANY places).
How do I wait synchronously for the new library to complete and invoke the callback function?
e.g. Should I use AutoResetEvents and call WaitOne?
Or are there better options?
How do I handle errors?
Would I create a wrapper and throw an exception in the failure callback function?
public void ExampleNewCode()
{
// Segment initialization and payload setup
string writeKey = "PqRStUv1WxYzraGHijkA1Pz0AbcDE12F";
Config configWithBatchingOff = new Config().SetAsync(false);
Segment.Analytics.Initialize(writeKey, configWithBatchingOff);
Context context = new Context() {{"appName", "MyApp"}};
Options optionsContext = new Options().SetContext(context);
Properties properties = new Properties() {{"my_first_prop", "foo bar"}};
// setup callback handlers
Analytics.Client.Failed += FailureHandler;
Analytics.Client.Succeeded += SuccessHandler;
Logger.Handlers += LogHandler;
// ****
// the old code structure I want to preserve. HOW DO I MAKE THIS WORK ???
// ****
List<string> users = new List<string>() { "user123", "user456" };
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
Analytics.Client.Track(userId, "Test Fired", properties, optionsContext);
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void FailureHandler(BaseAction action, System.Exception e)
{
Console.WriteLine($"FailureHandler called for userid={action.UserId}");
}
public void SuccessHandler(BaseAction action)
{
Console.WriteLine($"SuccessHandler called for userid={action.UserId}");
}
If I understand your question correctly then you can solve this problem by using TaskCompletionSource.
For the sake of simplicity I've created a dummy class which might raise an OnSuccess or an OnFailure event depending on the provided parameter:
public class Dummy
{
public event EventHandler OnFailure;
public event EventHandler OnSuccess;
public void DoWork(int i)
{
if (i % 2 == 0) OnFailure?.Invoke(this, null);
else OnSuccess?.Invoke(this, null);
}
}
On the consumer side you can do the following:
private static TaskCompletionSource<object> signalling = new TaskCompletionSource<object>();
public static async Task Main(string[] args)
{
Console.WriteLine("Calling new code");
var dummy = new Dummy();
dummy.OnSuccess += Dummy_OnSuccess;
dummy.OnFailure += Dummy_OnFailure;
dummy.DoWork(2);
try
{
await signalling.Task;
Console.WriteLine("New code has finished");
}
catch (Exception)
{
Console.WriteLine("New code has failed");
}
Console.WriteLine("Calling old code");
}
private static void Dummy_OnFailure(object sender, EventArgs e)
{
Thread.Sleep(1000);
signalling.TrySetException(new Exception("Operation failed"));
}
private static void Dummy_OnSuccess(object sender, EventArgs e)
{
Thread.Sleep(1000);
signalling.TrySetResult(null);
}
Whenever the OnFailure event is emitted then call the TrySetException on the TaskCompletionSource to indicate that the operation has been finished without luck
Whenever the OnSuccess event is emitted the we call the TrySetResult on the TaskCompletionSource to indicate that the operation has been finished with luck
We are await-ing the TaskCompletionSource that's why we can be sure that either the requested operation succeeded or failed after the line of await
Regarding using the segment library, the way you get to know if an action (Identify, Group, Track, Alias, Page or Screen) succeeds or fails when being sent to Segment Server is setting up callback handlers like you did (Failed, Succeeded) and also the Logger Handler.
As your example above, both Succeeded and Failed callbacks receive by parameter the BaseAction which allows you to have information regarding the Action that succeeded or failed.
As you have mentioned, Analytics.NET library sends it Actions asynchronously enqueuing them and when the queue is full then it proceeds to flush those actions and send them in a batch to the segment server. So if you want to send automatically to Segment's server once you invoke any of the Actions, you should call Analytics.Client.Flush() so as to get inmediate feedback on the Succeeded or Failed Callback and the Loggers handler. In your code it will be:
public void ExampleNewCode()
{
// Segment initialization and payload setup
string writeKey = "PqRStUv1WxYzraGHijkA1Pz0AbcDE12F";
Config configWithBatchingOff = new Config().SetAsync(false);
Segment.Analytics.Initialize(writeKey, configWithBatchingOff);
Context context = new Context() {{"appName", "MyApp"}};
Options optionsContext = new Options().SetContext(context);
Properties properties = new Properties() {{"my_first_prop", "foo bar"}};
// setup callback handlers
Analytics.Client.Failed += FailureHandler;
Analytics.Client.Succeeded += SuccessHandler;
Logger.Handlers += LogHandler;
List<string> users = new List<string>() { "user123", "user456" };
foreach (string userId in users)
{
try
{
Console.WriteLine($"Processing {userId} started");
Analytics.Client.Track(userId, "Test Fired", properties, optionsContext);
Analytics.Client.Flush();
Console.WriteLine($"Processing {userId} completed");
}
catch (Exception e)
{
Console.WriteLine($"Processing {userId} FAILED. {e.Message}");
}
}
}
public void FailureHandler(BaseAction action, System.Exception e)
{
Console.WriteLine($"FailureHandler called for userid={action.UserId}");
}
public void SuccessHandler(BaseAction action)
{
Console.WriteLine($"SuccessHandler called for userid={action.UserId}");
}
I add a dashboard devexpress that run a stored procedure and it takes a long time. So I created a simple thread in my application in form_load
public void LoadDashboard()
{
using (Stream s = new MemoryStream(Encoding.Default.GetBytes(Resource.Dashboard.MaterialDashboard1)))
{
s.Position = 0;
dashboardViewer1.LoadDashboard(s);
}
}
private void frmMaterialDashboard_Load(object sender, EventArgs e)
{
Thread newth=new Thread(LoadDashboard);
newth.Start();
int UserId = int.Parse(Configuration.AccountDetail.UserId.ToString());
lblUserName.Caption = _userRepository.Get().Where(i => i.Id == UserId).First().FullName;
alertControl1.Show(this, "Welcome","Welcome to SPMS Mr."+_userRepository.FindById(Configuration.AccountDetail.UserId).First().FullName +"\n Server time:"+DateTime.Now);
}
But when I run my application I get this error :
An unhandled exception of type 'DevExpress.DashboardCommon.DashboardInternalException' occurred in DevExpress.Dashboard.v15.2.Win.dll
Additional information: Internal error. Please contact the application vendor or your system administrator and provide the following information.
System.InvalidOperationException: The current SynchronizationContext may not be used as a TaskScheduler.
at System.Threading.Tasks.SynchronizationContextTaskScheduler..ctor()
at DevExpress.DashboardWin.Native.WinDashboardService.RequestCustomizationServices(RequestCustomizationServicesEventArgs e)
at DevExpress.DashboardCommon.Service.DashboardService.DevExpress.DashboardCommon.Service.IDashboardServiceAdminHandlers.OnRequestCustomizationServices(Object sender, RequestCustomizationServicesEventArgs e)
at DevExpress.DashboardCommon.Server.DashboardSession.CreateDataLoaderParameters(ReloadDataArgs args)
at DevExpress.DashboardCommon.Server.DashboardSession.CreateDataLoader(ReloadDataArgs args)
at DevExpress.DashboardCommon.Server.DashboardSession.LoadData(IEnumerable1 dataSourceComponentNames, ReloadDataArgs args)
at DevExpress.DashboardCommon.Server.DashboardSession.ReloadData(IEnumerable1 dataSourceComponentNames, ReloadDataArgs args)
at DevExpress.DashboardCommon.Server.DashboardSession.Initialize(DashboardSessionState state, Boolean isDesignMode)
at DevExpress.DashboardCommon.Service.DashboardServiceOperation`1.Execute(DashboardServiceResult result)
Updated
I change my code like this, it works without any error and the data is shown but without any async operation and I have to wait to load data
public async Task<Stream> LoadDashboard()
{
Stream s = new MemoryStream(Encoding.Default.GetBytes(Resource.Dashboard));
s.Position = 0;
return s;
}
private async void frmMaterialDashboard_Load(object sender, EventArgs e)
{
Stream dashboardData = await LoadDashboard();
dashboardViewer1.LoadDashboard(dashboardData);
int UserId = int.Parse(Configuration.AccountDetail.UserId.ToString());
lblUserName.Caption = _userRepository.Get().Where(i => i.Id == UserId).First().FullName;
alertControl1.Show(this, "Welcome","Welcome to SPMS Mr."+_userRepository.FindById(Configuration.AccountDetail.UserId).First().FullName +"\n Server time:"+DateTime.Now);
}
Without full context of the problem I can't give you an exact solution, but overall, you cannot access UI elements from another thread. That means you need to do all request and computation on another thread, and then update UI elements on UI thread. Consider such simplified solution that does not explicitly start a new thread:
// event on UI thread
private async void frmMaterialDashboard_Load(object sender, EventArgs e)
{
var dashboardData = await LoadDashboardDataFromDatabaseAsync();
dashboardViewer1.Load(dashboardData);
}
public async Task<DashboardData> LoadDashboardDataFromDatabaseAsync()
{
string query = "...";
var queryResult = await db.ExucuteQueryAsync(query).ConfigureAwait(false);
return ConvertQueryRequltToDashboardData(queryResult);
}
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 have an Excel Add-In written in C#, .NET 4.5. It will send many web service requests to a web server to get data. E.g. it sends 30,000 requests to web service server. When data of a request comes back, the addin will plot the data in Excel.
Originally I did all the requests asynchronously, but sometime I will get OutOfMemoryException
So I changed, sent the requests one by one, but it is too slow, takes long time to finish all requests.
I wonder if there is a way that I can do 100 requests at a time asynchronously, once the data of all the 100 requests come back and plot in Excel, then send the next 100 requests.
Thanks
Edit
On my addin, there is a ribbon button "Refresh", when it is clicked, refresh process starts.
On main UI thread, ribbon/button is clicked, it will call web service BuildMetaData,
once it is returned back, in its callback MetaDataCompleteCallback, another web service call is sent
Once it is returned back, in its callback DataRequestJobFinished, it will call plot to plot data on Excel. see below
RefreshBtn_Click()
{
if (cells == null) return;
Range firstOccurence = null;
firstOccurence = cells.Find(functionPattern, null,
null, null,
XlSearchOrder.xlByRows,
XlSearchDirection.xlNext,
null, null, null);
DataRequest request = null;
_reportObj = null;
Range currentOccurence = null;
while (!Helper.RefreshCancelled)
{
if(firstOccurence == null ||IsRangeEqual(firstOccurence, currentOccurence)) break;
found = true;
currentOccurence = cells.FindNext(currentOccurence ?? firstOccurence);
try
{
var excelFormulaCell = new ExcelFormulaCell(currentOccurence);
if (excelFormulaCell.HasValidFormulaCell)
{
request = new DataRequest(_unityContainer, XLApp, excelFormulaCell);
request.IsRefreshClicked = true;
request.Workbook = Workbook;
request.Worksheets = Worksheets;
_reportObj = new ReportBuilder(_unityContainer, XLApp, request, index, false);
_reportObj.ParseParameters();
_reportObj.GenerateReport();
//this is necessary b/c error message is wrapped in valid object DataResponse
//if (!string.IsNullOrEmpty(_reportObj.ErrorMessage)) //Clear previous error message
{
ErrorMessage = _reportObj.ErrorMessage;
Errors.Add(ErrorMessage);
AddCommentToCell(_reportObj);
Errors.Remove(ErrorMessage);
}
}
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
Errors.Add(ErrorMessage);
_reportObj.ErrorMessage = ErrorMessage;
AddCommentToCell(_reportObj);
Errors.Remove(ErrorMessage);
Helper.LogError(ex);
}
}
}
on Class to GenerateReport
public void GenerateReport()
{
Request.ParseFunction();
Request.MetacompleteCallBack = MetaDataCompleteCallback;
Request.BuildMetaData();
}
public void MetaDataCompleteCallback(int id)
{
try
{
if (Request.IsRequestCancelled)
{
Request.FormulaCell.Dispose();
return;
}
ErrorMessage = Request.ErrorMessage;
if (string.IsNullOrEmpty(Request.ErrorMessage))
{
_queryJob = new DataQueryJob(UnityContainer, Request.BuildQueryString(), DataRequestJobFinished, Request);
}
else
{
ModifyCommentOnFormulaCellPublishRefreshEvent();
}
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
ModifyCommentOnFormulaCellPublishRefreshEvent();
}
finally
{
Request.MetacompleteCallBack = null;
}
}
public void DataRequestJobFinished(DataRequestResponse response)
{
Dispatcher.Invoke(new Action<DataRequestResponse>(DataRequestJobFinishedUI), response);
}
public void DataRequestJobFinished(DataRequestResponse response)
{
try
{
if (Request.IsRequestCancelled)
{
return;
}
if (response.status != Status.COMPLETE)
{
ErrorMessage = ManipulateStatusMsg(response);
}
else // COMPLETE
{
var tmpReq = Request as DataRequest;
if (tmpReq == null) return;
new VerticalTemplate(tmpReq, response).Plot();
}
}
catch (Exception e)
{
ErrorMessage = e.Message;
Helper.LogError(e);
}
finally
{
//if (token != null)
// this.UnityContainer.Resolve<IEventAggregator>().GetEvent<DataQueryJobComplete>().Unsubscribe(token);
ModifyCommentOnFormulaCellPublishRefreshEvent();
Request.FormulaCell.Dispose();
}
}
on plot class
public void Plot()
{
...
attributeRange.Value2 = headerArray;
DataRange.Value2 = ....
DataRange.NumberFormat = ...
}
OutOfMemoryException is not about the too many requests sent simultaneously. It is about freeing your resources right way. In my practice there are two main problems when you are getting such exception:
Wrong working with immutable structures or System.String class
Not disposing your disposable resources, especially graphic objects and WCF requests.
In case of reporting, for my opinion, you got a second one type of a problem. DataRequest and DataRequestResponse are good point to start the investigation for the such objects.
If this doesn't help, try to use the Tasks library with async/await pattern, you can find good examples here:
// Signature specifies Task<TResult>
async Task<int> TaskOfTResult_MethodAsync()
{
int hours;
// . . .
// Return statement specifies an integer result.
return hours;
}
// Calls to TaskOfTResult_MethodAsync
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();
// Signature specifies Task
async Task Task_MethodAsync()
{
// . . .
// The method has no return statement.
}
// Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync();
await returnedTask;
// or, in a single statement
await Task_MethodAsync();
In your code I see a while loop, in which you can store your Task[] of size of 100, for which you can use the WaitAll method, and the problem should be solved. Sorry, but your code is huge enough, and I can't provide you a more straight example.
I'm having a lot of trouble parsing your code to figure out is being iterated for your request but the basic template for batching asynchronously is going to be something like this:
static const int batchSize = 100;
public async Task<IEnumerable<Results>> GetDataInBatches(IEnumerable<RequestParameters> parameters) {
if(!parameters.Any())
return Enumerable.Empty<Result>();
var batchResults = await Task.WhenAll(parameters.Take(batchSize).Select(doQuery));
return batchResults.Concat(await GetDataInBatches(parameters.Skip(batchSize));
}
where doQuery is something with the signature
Task<Results> async doQuery(RequestParameters parameters) {
//.. however you do the query
}
I wouldn't use this for a million requests since its recursive, but your case should would generate a callstack only 300 deep so you'll be fine.
Note that this also assumes that your data request stuff is done asynchronously and returns a Task. Most libraries have been updated to do this (look for methods with the Async suffix). If it doesn't expose that api you might want to create a separate question for how to specifically get your library to play nice with the TPL.
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
}