I am writing a Visual Studio extension in C# and I get a strange behavior on managing exception and displaying error messages. Basically, I just want to add some details to the exception message to help me investigate in case of a problem.
It all starts from a command on a context menu item and I suspect it may be related to threads management behind the async/await mechanism. But I am not sure I guess correctly and I am not able to find any solution. HELP!
It starts from my menu item callback:
internal sealed class My_RunAnalysis
{
//...
public static async Task InitializeAsync(AsyncPackage package)
{
// Switch to the main thread - the call to AddCommand in PS_RunAnalysis's constructor requires
// the UI thread.
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
OleMenuCommandService commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService;
Instance = new My_RunAnalysis(package, commandService);
}
//...
private async void ExecuteAsync(object sender, EventArgs e)
{
try
{
await My_ViewModel.RunAnalysisAsync();
}
catch (Exception exc)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
MessageBox.Show(exc.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
//...
class My_ViewModel
{
async public static Task RunAnalysisAsync()
{
await My_Model.GetResultsListAsync();
}
}
//...
class My_Model
async public static Task GetResultsListAsync()
{
ResultsList = new My_ResultsList();
var rawResultsList = await QueryServerAsync<RawResultsListResponse>("GET", My_Request.GetResults());
//...
}
async public static Task<JsonResponse> QueryServerAsync<JsonResponse>(string method,
string request)
{
try
{
HttpResponseMessage response;
switch (method)
{
case "GET":
response = await _httpClient.GetAsync(request);
break;
case "POST":
default:
StringContent httpContent = new StringContent("", Encoding.UTF8, "application/json");
response = await _httpClient.PostAsync(request, httpContent);
break;
}
if (!response.IsSuccessStatusCode) //<<<<<<CASE #1
{
throw new My_Exception(
response.ReasonPhrase,
"Exception while querying server for " + request);
}
string serializedJson = await response.Content.ReadAsStringAsync();
// CASE #2>>>>>
var jsonResponse = serializer.Deserialize<JsonResponse>(serializedJson);
return jsonResponse;
}
catch (Exception e)
{
throw new My_Exception(
e.Message,
"Exception while querying server for " + request);
}
}
The strange thing is that:
When an error occurs in case #1 and I create a custom exception (my server responded but there was an internal error and I have a clean error code), the MessageBox in the catch of My_ViewModel::RunAnalysisAsync() will show correctly and immediately.
When a native exception occurs in case #2 (my server responded with malformed json and I get an exception from serializer.Deserialize), the MessageBox in the catch of My_ViewModel::RunAnalysisAsync() will not show, the IDE will hang for around 15s before restarting (and still not show the MessageBox).
Any idea what's wrong?
Thanks!
EDIT:
Seeing that the template for my custom command initializes also with SwitchToMainThreadAsync, I have tried to do the same with the Execute method. I updated the code above but it still does not work: an exception thrown by serializer.Deserialize will still freeze the UI for 10 to 15s and the MessageBox will not show!
Also note that the debugger can step immediately on "await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);" and go on next step to MessageBox. I would tend to suppose it means that the switch to the main thread is immediate but there is still something wrong...
Any idea what's wrong? I really need to capture exceptions a reliable way...
I could not find any explanation to the MessageBox working on a case and not on the other one. I ended up going to some log solution using FileStream.WriteAsync. Hence everything keeps async and I don't have to use MessageBox anymore.