In Steven Toub's article:
To make it easier for developers to write asynchronous code based on
Tasks, .NET 4.5 changes the default exception behavior for unobserved
exceptions. While unobserved exceptions will still cause the
UnobservedTaskException event to be raised (not doing so would be a
breaking change), the process will not crash by default. Rather, the
exception will end up getting eaten after the event is raised,
regardless of whether an event handler observes the exception.
But the result of my experiment does not match the above statement. Below is my code:
static void Main(string[] args)
{
DownloadAsync("http://an.invalid.url.com);
}
async static void DownloadAsync(string url)
{
using (var client = new System.Net.Http.HttpClient())
{
string text = await client.GetStringAsync(url);
Console.WriteLine("Downloaded {0} chars", text.Length);
}
}
Since I pass an invalid url to DownloadAsync() method, the call to HttpClient's GetStringAsync() method will throw an expcetion, and it crashes the application.
So my question is: Does unobserved exceptions in .NET 4.5 still crash app by default?
You do have a Task with an exception (the one returned by GetStringAsync). However, the await is observing the Task exception, which then propagates out of the DownloadAsync method (which is async void).
Exceptions propagating out of async void methods behave differently; they are raised on the SynchronizationContext that was active when the async void method started (in this case, a thread pool SynchronizationContext). This is not considered an unobserved exception.
If you change DownloadAsync to return Task, then you will have an actual unobserved Task exception, which will be ignored (correctly):
static void Main(string[] args)
{
DownloadAsync("http://an.invalid.url.com);
Console.ReadKey();
}
async static Task DownloadAsync(string url)
{
using (var client = new System.Net.Http.HttpClient())
{
string text = await client.GetStringAsync(url);
Console.WriteLine("Downloaded {0} chars", text.Length);
}
}
Related
I'm designing some code right now where I'm throwing an exception if a string parameter is null or empty and the exception is thrown as it should be, but it isn't getting caught when I'm UnitTesting.
Here's the client I'm using.
public class PipeClient : IPipeClient
{
public async void Send(string host, string pipeName, Message msg)
{
if (string.IsNullOrEmpty(msg.PreparedMessage))
throw new ArgumentException("MESSAGE_NOT_FOUND");
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(pipeName))
throw new ArgumentNullException();
if (!host.TryParseHost())
throw new ArgumentException("INVALID_HOST_NAME");
using (var pipeClient = new NamedPipeClientStream(host, pipeName, PipeDirection.Out))
{
pipeClient.Connect(200);
using (var writer = new StreamWriter(pipeClient))
{
await Task.Run(() => writer.WriteLine(msg.PreparedMessage));
writer.Flush();
}
}
}
}
And here's the UnitTest
[TestMethod]
public void Send_FailsOnWrongHostName()
{
var name = "FailWithHostname";
var msg = new Message(MyStates.Register, "UnitTest", "Test");
try
{
var client = new PipeClient();
client.Send("lol", name, msg);
}
catch (Exception e)
{
Assert.IsTrue(e is ArgumentException);
}
}
So when I run that test it should as far as I know throw the exception when I call the the Send method (which is does) and then get caught in the catch clause because I'm not catching it inside the PipeClient. Yet it doesn't, it just exits with a failed test.
If you need any more information just let me know, thanks in advance.
there's a few things I want to raise in this answer. I'm not sure of your experience level so please don't think I'm being condescending at any point.
Firstly a brief note on async methods and Tasks.
Async void should be avoided unless in an async event handler. Async methods should return Task or Task otherwise there is nothing for the calling method to keep hold of to know when the method is done and to report back whether the method threw an exception. Async void is essentially fire and forget, there is no one left to observe the exceptions.
"In observed Tasks no one can you scream" -Me ,2018
Exceptions thrown in async methods are nicely unwrapped and thrown
when the async method is awaited, with the call stack all preserved
and reasonably sensible. If you don't await the result eventually at
some point in the future you will get an UnobservedTaskException
that, if you haven't configured a global handler for, will bring down
your application. If you get the result of an async method
synchronously using .Wait() or .Result or via
.GetAwaiter().GetResult() (all 3 you should try and avoid but the 3rd
option is best if you have to I have been informed), then you will
get the original exception wrapped in an AggregateException.
Now if none of this is making much sense to you, I would recommend doing some reading up Tasks and async/await.
Now onto your Test.
Your method is async void so there is nothing for the calling method to have returned to it to represent the work or to let it know that the method has thrown an exception. So it carries on, the test finishes and then everything completes with no exceptions because the UnobservedTaskException can be thrown at anypoint in the future (I think it is related to when the garbage collector tidies up the faulted Task and then it throws and because the garbage collector is non-deterministic we can't say when that will happen)
So what if you made your async method return a Task??? Well that's still not quite right. You are now returning a Task that will be in a faulted state because of the exception, however because you never await it, the exception is never 'unwrapped' and actually thrown and so you're test happily continues.
What you need to do is make your Test async and return a Task and make the method you're testing async Task not async void and await that method in your test.
Like this
[TestMethod]
public async Task Send_FailsOnWrongHostName()
{
var name = "FailWithHostname";
var msg = new Message(MyStates.Register, "UnitTest", "Test");
try
{
var client = new PipeClient();
await client.Send("lol", name, msg);
}
catch (Exception e)
{
Assert.IsTrue(e is ArgumentException);
}
}
public class PipeClient : IPipeClient
{
public async Task Send(string host, string pipeName, Message msg)
{
if (string.IsNullOrEmpty(msg.PreparedMessage))
throw new ArgumentException("MESSAGE_NOT_FOUND");
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(pipeName))
throw new ArgumentNullException();
if (!host.TryParseHost())
throw new ArgumentException("INVALID_HOST_NAME");
using (var pipeClient = new NamedPipeClientStream(host, pipeName, PipeDirection.Out))
{
pipeClient.Connect(200);
using (var writer = new StreamWriter(pipeClient))
{
await Task.Run(() => writer.WriteLine(msg.PreparedMessage));
writer.Flush();
}
}
}
}
Taken from article on async await by Stephen Cleary:
Figure 2 Exceptions from an Async Void Method Can’t Be Caught with Catch
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught here!
throw;
}
}
... 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...
What does that actually mean? I wrote an extended example to try and glean more info. It has the same behaviour as Figure 2:
static void Main()
{
AppDomain.CurrentDomain.UnhandledException += (sender, ex) =>
{
LogCurrentSynchronizationContext("AppDomain.CurrentDomain.UnhandledException");
LogException("AppDomain.CurrentDomain.UnhandledException", ex.ExceptionObject as Exception);
};
try
{
try
{
void ThrowExceptionVoid() => throw new Exception("ThrowExceptionVoid");
ThrowExceptionVoid();
}
catch (Exception ex)
{
LogException("AsyncMain - Catch - ThrowExceptionVoid", ex);
}
try
{
// CS1998 C# This async method lacks 'await' operators and will run synchronously.
async void ThrowExceptionAsyncVoid() => throw new Exception("ThrowExceptionAsyncVoid");
ThrowExceptionAsyncVoid();
}
// exception cannot be caught, despite the code running synchronously.
catch (Exception ex)
{
LogException("AsyncMain - Catch - ThrowExceptionAsyncVoid", ex);
}
}
catch (Exception ex)
{
LogException("Main", ex);
}
Console.ReadKey();
}
private static void LogCurrentSynchronizationContext(string prefix)
=> Debug.WriteLine($"{prefix} - " +
$"CurrentSynchronizationContext: {SynchronizationContext.Current?.GetType().Name} " +
$"- {SynchronizationContext.Current?.GetHashCode()}");
private static void LogException(string prefix, Exception ex)
=> Debug.WriteLine($"{prefix} - Exception - {ex.Message}");
Debug output:
Exception thrown: 'System.Exception' in ConsoleApp3.dll
AsyncMain - Catch - ThrowExceptionVoid - Exception - ThrowExceptionVoid
Exception thrown: 'System.Exception' in ConsoleApp3.dll
An exception of type 'System.Exception' occurred in ConsoleApp3.dll but was not handled in user code
ThrowExceptionAsyncVoid
AppDomain.CurrentDomain.UnhandledException - CurrentSynchronizationContext: -
AppDomain.CurrentDomain.UnhandledException - Exception - ThrowExceptionAsyncVoid
The thread 0x1c70 has exited with code 0 (0x0).
An unhandled exception of type 'System.Exception' occurred in System.Private.CoreLib.ni.dll
ThrowExceptionAsyncVoid
The program '[18584] dotnet.exe' has exited with code 0 (0x0).
I want more details
If there is no current synchronization context (as in my example), where is the exception raised?
What are the differences between async void (with no await) and void
The compiler warns CS1998 C# This async method lacks 'await' operators and will run synchronously.
If it runs synchronously with no await, why is it behaving differently from simply void?
Does async Task with no await also behave differently from Task?
What are the differences in compiler behaviour between async void and async Task. Is a Task object really created under-the-hood for async void as suggested here?
Edit. To be clear, this isn't a question about best practices - it is a question about compiler / runtime implementation.
If there is no current synchronization context (as in my example), where is the exception raised?
By convention, when SynchronizationContext.Current is null, that's really the same as SynchronizationContext.Current equal to an instance of new SynchronizationContext(). In other words, "no synchronization context" is the same as the "thread pool synchronization context".
So, the behavior you're seeing is that the async state machine is catching the exception and then raising it directly on a thread pool thread, where it cannot be caught with catch.
This behavior seems odd, but think about it this way: async void is intended for event handlers. So consider a UI application raising an event; if it is synchronous, then any exceptions get propagated to the UI message processing loop. The async void behavior is intended to mimic that: any exceptions (including ones after await) are re-raised on the UI message processing loop. This same logic is applied to the thread pool context; e.g., exceptions from your synchronous System.Threading.Timer callback handler will be raised directly on the thread pool, and so will exceptions from your asynchronous System.Threading.Timer callback handler.
If it runs synchronously with no await, why is it behaving differently from simply void?
The async state machine is handling the exceptions specially.
Does async Task with no await also behave differently from Task?
Absolutely. async Task has a very similar state machine - it catches any exceptions from your code and places them on the returned Task. This is one of the pitfalls in eliding async/await for non-trivial code.
What are the differences in compiler behaviour between async void and async Task.
For the compiler, the difference is just how exceptions are handled.
The proper way to think about this is that async Task is a natural and appropriate language development; async void is a weird hack that the C#/VB team adopted to enable asynchronous events without huge backwards-compatibility issues. Other async/await-enabled languages such as F#, Python, and JavaScript have no concept of async void... and thus avoid all of its pitfalls.
Is a Task object really created under-the-hood for async void as suggested here?
No.
async void - It can't be awaited and it allows you to fire or forget methods
async Task - It can be awaited, but does not return any value
async Task methodName { return default(T); } - It can be awaited, and returns a value of the type T
void - no argument will be returned
Consider this code:
public IEnumerable<string> GetAmazonMwsNotifications(ScAmazonNotificationType scAmazonNotificationType, CancellationToken cancellationToken)
{
var scAmazonSqsMwsNotificationsManagmentClientRequestBuilder = _scServiceLocator.GetInstance<IScAmazonSqsMwsNotificationsManagmentClientRequestBuilder>();
var blockingCollection = new BlockingCollection<string>();
try
{
StartReceiveMessagesAsync(blockingCollection, cancellationToken, scAmazonNotificationType, scAmazonSqsMwsNotificationsManagmentClientRequestBuilder);
}
catch (Exception exception)
{
throw //this catch is never called;
}
return blockingCollection.GetConsumingEnumerable(cancellationToken);
}
private async void StartReceiveMessagesAsync(BlockingCollection<string> blockingCollection, CancellationToken cancellationToken, ScAmazonNotificationType scAmazonNotificationType, IScAmazonSqsMwsNotificationsManagmentClientRequestBuilder scAmazonSqsMwsNotificationsManagmentClientRequestBuilder)
{
var semaphore = new SemaphoreSlim(15);
var receiveMessageRequest = scAmazonSqsMwsNotificationsManagmentClientRequestBuilder.BuildReceiveMessageRequest(scAmazonNotificationType);
while (!cancellationToken.IsCancellationRequested)
{
await semaphore.WaitAsync(cancellationToken);
Task.Factory.StartNew(() =>
{
try
{
throw new ApplicationException("Test");
var receiveMessageResponse = _scAmazonSqsClientWrapper.ReceiveMessageAsync(receiveMessageRequest, cancellationToken).Result;
foreach (var result in receiveMessageResponse.Messages.Select(p => p.Body))
{
blockingCollection.Add(result, cancellationToken);
}
var deleteFromQueueRequest = scAmazonSqsMwsNotificationsManagmentClientRequestBuilder.BuildBatchDeleteMessageRequest(scAmazonNotificationType, receiveMessageResponse.Messages.Select(p => p.ReceiptHandle).ToArray());
_scAmazonSqsClientWrapper.DeleteMessageBatchAsync(deleteFromQueueRequest, cancellationToken);
}
finally
{
semaphore.Release(1);
}
}, cancellationToken, TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent, new ThreadPerTaskScheduler());
}
}
If the exception is thrown inside the task delegate it's never propagated to the calling method. I can't await for task inside the semaphore, because in this case semaphore will be blocked by the awaited task. Is there any way to propagate the exception to the calling method.
You're running in two problems:
async void has a slightly different error handling in comparision to traditional void methods: Although StartReceiveMessagesAsync is called by GetAmazonMwsNotifications and it blocks GetAmazonMwsNotifications until the first await (on an uncompleted Task) is reached, any exceptions from within StartReceiveMessagesAsync are never thrown back to GetAmazonMwsNotifications. In non UI applications they are always thrown onto the threadpool, bringing the application down (I don't know how UI applications work in that case).
So why doesn't your application die?
The exception is not thrown onto the stack, it's set onto the Task that is returned by Task.Factory.StartNew and this Task is not observed (neither via await nor via .Wait()). At some point the Garbage Collector will run to collect that Task and at that point an UnobservedTaskException will be thrown on the appdomain. When this is not observed your application will finally go down.
In my opinion you don't need to offload the code via Task.Run/Task.Factory.StartNew, await the result of ReceiveMessageAsync instead of blocking on it and handle exceptions in the async void method the "usual" way; just keep in mind that unhandled exceptions will bring down the application.
You can continue with another task with the OnlyOnFaulted option (i.e. an exception has been thrown and the task is in the faulted state).
.ContinueWith(t => { Console.WriteLine(t.Exception.Message); },
TaskContinuationOptions.OnlyOnFaulted);
I've tried to read up on async methods and am now trying to create my own async method. The method is a webservice call that returns a list of error logs. I'm not sure that I've understood correctly so I thought I'd share my code to see if I should do anything different.
All I want the code to do is return a list of errorlogs by calling a method GetAllErrorLogs(), that is a synchronized method. Since it can take a second to fetch all the error logs I want to have the opportunity to do other stuff once I called the GetAllErrorLogs() method. Here is the code.
[WebMethod]
public async Task<List<ErrorLog>> GetAllErrorLogs()
{
List<ErrorLog> errorLogs = new List<ErrorLog>();
await System.Threading.Tasks.Task.Run(() => {
errorLogs = ErrorLogRepository.GetAllErrorLogs();
});
if (errorLogs == null)
return new List<ErrorLog>();
return errorLogs;
}
Thanks!
I recently gave a talk at ThatConference on async on the server side, and I address this issue in the slides.
On the server side, you want to avoid the use of Task.Run and other constructs that queue work to the thread pool. As much as possible, keep thread pool threads available for handling requests.
So, ideally your repository would have an asynchronous method GetAllErrorLogsAsync, which would itself be asynchronous. If GetAllErrorLogs cannot be asynchronous, then you may as well just call it directly (removing the await Task.Run).
Since it can take a second to fetch all the error logs I want to have the opportunity to do other stuff once I called the GetAllErrorLogs() method.
If you have a GetAllErrorLogsAsync available, then this can easily be done using Task.WhenAll. However, if GetAllErrorLogs is synchronous, then you can only do this by doing parallel work in your request (e.g., multiple calls to Task.Run followed by Task.WhenAll).
Parallel code on the server must be approached with great trepidation. It is only acceptable in a very limited set of scenarios. The entire point of async on the server side is to use fewer threads per request, and when you start parallelizing, you're doing the opposite: multiple threads per request. This is only appropriate if you know your user base is very small; otherwise, you'll kill your server scalability.
I found this great codeproject detailed article about how to achieve that
http://www.codeproject.com/Articles/600926/Asynchronous-web-services-call-in-ASP-NET
**This is potentially wrong, read comments or spinoff question at HttpContext.Current after an await
If ErrorLogRepository.GetAllErrorLogs() is not thread-safe, it will cause weird bugs and potentially exception out. Make sure your code is ready for multi-threaded operation before switching to async methods, this is obviously very trivial advice but often overlooked. For example, if you reference HttpContext.Current in your methods, your code will die in the async method, and sometimes even AFTER the await. The reason is that the code within the async block will potentially be run on a separate thread, which will not have access to the same HttpContext.Current thread-static property, and await gets compiled into two methods. All code before an await gets run on one thread, and then calls the code after an await keyword as a continuation, but potentially on yet another thread. So sometimes your code will even work in an async block, only to choke unexpectedly after it gets "out" of the async back to what you think is a synchronous part of your code (but in reality everything after an await keyword is already not guaranteed to be the original thread).
Here is some production code...
using System.Web.Http;
using AysncTask = System.Threading.Tasks.Task;
public class myController : ApiControllerBase
{
[HttpPut]
[Route("api/cleardata/{id}/{requestId}/")]
public async AysncTask ClearData(Guid id, Guid requestId)
{
try
{
await AysncTask.Run(() => DoClearData(id, requestId));
}
catch (Exception ex)
{
throw new Exception("Exception in myController.ClearData", ex);
}
}
}
Handling Async exceptions is also VERY VERY important.. although this is for a windows console app, the same principles should apply.
source: https://blogs.msdn.microsoft.com/ptorr/2014/12/10/async-exceptions-in-c/
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncAndExceptions
{
class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += (s, e) => Log("*** Crash! ***", "UnhandledException");
TaskScheduler.UnobservedTaskException += (s, e) => Log("*** Crash! ***", "UnobservedTaskException");
RunTests();
// Let async tasks complete...
Thread.Sleep(500);
GC.Collect(3, GCCollectionMode.Forced, true);
}
private static async Task RunTests()
{
try
{
// crash
// _1_VoidNoWait();
// crash
// _2_AsyncVoidAwait();
// OK
// _3_AsyncVoidAwaitWithTry();
// crash - no await
// _4_TaskNoWait();
// crash - no await
// _5_TaskAwait();
// OK
// await _4_TaskNoWait();
// OK
// await _5_TaskAwait();
}
catch (Exception ex) { Log("Exception handled OK"); }
// crash - no try
// await _4_TaskNoWait();
// crash - no try
// await _5_TaskAwait();
}
// Unsafe
static void _1_VoidNoWait()
{
ThrowAsync();
}
// Unsafe
static async void _2_AsyncVoidAwait()
{
await ThrowAsync();
}
// Safe
static async void _3_AsyncVoidAwaitWithTry()
{
try { await ThrowAsync(); }
catch (Exception ex) { Log("Exception handled OK"); }
}
// Safe only if caller uses await (or Result) inside a try
static Task _4_TaskNoWait()
{
return ThrowAsync();
}
// Safe only if caller uses await (or Result) inside a try
static async Task _5_TaskAwait()
{
await ThrowAsync();
}
// Helper that sets an exception asnychronously
static Task ThrowAsync()
{
TaskCompletionSource tcs = new TaskCompletionSource();
ThreadPool.QueueUserWorkItem(_ => tcs.SetException(new Exception("ThrowAsync")));
return tcs.Task;
}
internal static void Log(string message, [CallerMemberName] string caller = "")
{
Console.WriteLine("{0}: {1}", caller, message);
}
}
}
Under what scenarios would one want to use
public async Task AsyncMethod(int num)
instead of
public async void AsyncMethod(int num)
The only scenario that I can think of is if you need the task to be able to track its progress.
Additionally, in the following method, are the async and await keywords unnecessary?
public static async void AsyncMethod2(int num)
{
await Task.Factory.StartNew(() => Thread.Sleep(num));
}
Normally, you would want to return a Task. The main exception should be when you need to have a void return type (for events). If there's no reason to disallow having the caller await your task, why disallow it?
async methods that return void are special in another aspect: they represent top-level async operations, and have additional rules that come into play when your task returns an exception. The easiest way is to show the difference is with an example:
static async void f()
{
await h();
}
static async Task g()
{
await h();
}
static async Task h()
{
throw new NotImplementedException();
}
private void button1_Click(object sender, EventArgs e)
{
f();
}
private void button2_Click(object sender, EventArgs e)
{
g();
}
private void button3_Click(object sender, EventArgs e)
{
GC.Collect();
}
f's exception is always "observed". An exception that leaves a top-level asynchronous method is simply treated like any other unhandled exception. g's exception is never observed. When the garbage collector comes to clean up the task, it sees that the task resulted in an exception, and nobody handled the exception. When that happens, the TaskScheduler.UnobservedTaskException handler runs. You should never let this happen. To use your example,
public static async void AsyncMethod2(int num)
{
await Task.Factory.StartNew(() => Thread.Sleep(num));
}
Yes, use async and await here, they make sure your method still works correctly if an exception is thrown.
For more information see: https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
I have come across this very useful article about async and void written by Jérôme Laban:
https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html
The bottom line is that an async+void can crash the system and usually should be used only on the UI side event handlers.
The reason behind this is the Synchronization Context used by the
AsyncVoidMethodBuilder, being none in this example. When there is no
ambient Synchronization Context, any exception that is unhandled by
the body of an async void method is rethrown on the ThreadPool. While
there is seemingly no other logical place where that kind of unhandled
exception could be thrown, the unfortunate effect is that the process
is being terminated, because unhandled exceptions on the ThreadPool
effectively terminate the process since .NET 2.0. You may intercept
all unhandled exception using the AppDomain.UnhandledException event,
but there is no way to recover the process from this event.
When writing UI event handlers, async void methods are somehow
painless because exceptions are treated the same way found in
non-async methods; they are thrown on the Dispatcher. There is a
possibility to recover from such exceptions, with is more than correct
for most cases. Outside of UI event handlers however, async void
methods are somehow dangerous to use and may not that easy to find.
The problem with calling async void is that
you don’t even get the task back. You have no way of knowing when the function’s task has completed. —— Crash course in async and await | The Old New Thing
Here are the three ways to call an async function:
async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }
In all the cases, the function is transformed into a chain of tasks. The difference is what the function returns.
In the first case, the function returns a task that eventually produces the t.
In the second case, the function returns a task which has no product, but you can
still await on it to know when it has run to completion.
The third case is the nasty one. The third case is like the second case, except
that you don't even get the task back. You have no way of knowing when
the function's task has completed.
The async void case is a "fire and
forget": You start the task chain, but you don't care about when it's
finished. When the function returns, all you know is that everything
up to the first await has executed. Everything after the first await
will run at some unspecified point in the future that you have no
access to.
I think you can use async void for kicking off background operations as well, so long as you're careful to catch exceptions. Thoughts?
class Program {
static bool isFinished = false;
static void Main(string[] args) {
// Kick off the background operation and don't care about when it completes
BackgroundWork();
Console.WriteLine("Press enter when you're ready to stop the background operation.");
Console.ReadLine();
isFinished = true;
}
// Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
static async void BackgroundWork() {
// It's important to catch exceptions so we don't crash the appliation.
try {
// This operation will end after ten interations or when the app closes. Whichever happens first.
for (var count = 1; count <= 10 && !isFinished; count++) {
await Task.Delay(1000);
Console.WriteLine($"{count} seconds of work elapsed.");
}
Console.WriteLine("Background operation came to an end.");
} catch (Exception x) {
Console.WriteLine("Caught exception:");
Console.WriteLine(x.ToString());
}
}
}
A brief explanation:
async Task<T> method() await can be used to wait till the execution is completed and it will return value of type T
async Task method() await can be used to wait till the execution is completed but no data is returned
async void method() can't be awaited and no data is returned [Example: async event execution]
My answer is simple
you can not await void method
Error CS4008 Cannot await 'void' TestAsync e:\test\TestAsync\TestAsyncProgram.cs
So if the method is async, it is better to be awaitable, because you can lose the advantage ofasync.