Async Programming without Await? - c#

I'm working on a series of methods that execute many different database calls using entity framework. Many of these methods can run asynchronously as I really don't care about their output, and don't rely on them.
However, when I try implementing certain methods, I get a warning from the compiler saying: "Because this call is not awaited, the current method continues to run before the call is completed"
But to me, this seems like that's my desired behavior, as I don't care what the methods do.
Here's an example of the methods
public async Task SetupAccessControl(int objectTypeId, int objectId, int? organizationId)
{
using (var context = new SupportContext(CustomerId))
{
... // omitted for brevity
if (objectTypeId == (int) ObjectType.User)
{
AddToUserRoleBridge("Everyone", objectId);//Warning on this line
AddToUserRoleBridge("Default", objectId); //Warning on this line
}
... // omitted for brevity
}
}
public async Task AddToUserRoleBridge(string role, int userId)
{
using (var context = new SupportContext(CustomerId))
{
var defaultRole = context.Roles.FirstOrDefault(n => n.Name == role);
if (defaultRole != null)
{
var urb = new UserRoleBridge
{
RoleId = defaultRole.Id,
UserId = userId
};
context.UserRoleBridges.Add(urb);
await context.SaveChangesAsync();
}
}
}
edit
Essentially, when I run the main function, I want a series of method calls to all fire off somewhat simultaneously and handle everything in their own threads so that I don't have to worry about it. Here is a pseudo-code example.
public async void RunAllAsync() {
taskA(*some value*);
taskA(*some value*);
taskB(*some value*);
taskB(*some value*);
await AllTasksCompleted
}
public async Task taskA(int item){
//do something with item
}
public async Task taskB(int item) {
subTaskB(*some value*)
subTaskB(*some value*)
}
public async Task subTaskB(int item) {
// do something
}
In the above example, when #RunAllAsync is called, every function call it makes (and the function calls they make) are fired off simultaneously. When all of these calls are completed, whatever method called #RunAllAsync would continue to execute.

If you're not using await, the async keyword doesn't really do anything useful and you can leave it off. You can await a method returning a Task regardless of whether it's marked as async or not.
public Task DoSomethingAsync()
{
Task someTaskJustLikeANormalReturnValue = Task.Delay(1000);
return someTaskJustLikeANormalReturnValue;
}
// later...
public async Task SomeOtherFunction()
{
// You can await DoSomethingAsync just like any async method, because
// you're really awaiting the Task which got returned.
await DoSomethingAsync();
}
In your case I would probably collect the tasks and await them all together:
public async Task SetupAccessControl(int objectTypeId, int objectId, int? organizationId)
{
var tasks = new List<Task>();
using (var context = new SupportContext(CustomerId))
{
... // omitted for brevity
if (objectTypeId == (int) ObjectType.User)
{
tasks.Add(AddToUserRoleBridge("Everyone", objectId));
tasks.Add(AddToUserRoleBridge("Default", objectId));
}
... // omitted for brevity
}
await Task.WhenAll(tasks.ToArray());
}
This allows you to pass the decision up to the caller of whether to await on the subtasks or not. This will also allow the any caller to unwrap any exceptions if they happen in AddToUserRoleBridge.

You can do it two ways:
Change the signature of AddToUserRoleBridge to return void will
remove the warning (but nothing can await it then).
Store the result of the call. var ignoreme=AddToUserRoleBridge(...) will remove the warning as well.

Related

Race condition with async/await, how to resolve

I have a problem with async/await in C#, i need it to get some object called Trades, after i get it, it needs to SAVE it. Problem is, with async/await, it is doing the SAVE first, and then go and get my trade objects. How do i ensure i get the objects first, and then does the saving.... here is my code...
private async void OnRefresh()
{
try
{
var trades = await ExchangeServiceInstance.GetTrades("");
mmTrades = new ObservableCollection<EEtrade>(trades);
tradeListView.ItemsSource = mmTrades;
}
catch { }
}
public async void OnSignalReceived()
{
// THIS NEEDS TO FINISH FIRST, BUT IT DOESN'T
await tradeListView.Dispatcher.InvokeAsync((Action)async delegate
{
if (ExchangeServiceInstance.SelectedTabIndex == CURRENT_TAB_INDEX_ITEM)
{
await Task.Delay(MMConfig.DELAYMILLISEC);
OnRefresh();
}
});
// SOMEHOW THIS GETS CALLED FIRST BEFORE THE ABOVE GETS TO FINISH!
await OnSaveTrades();
}
public async Task<int> OnSaveTrades()
{
foreach (var trade in mmTrades)
{
await ExchangeServiceInstance.OnInsertDoneTrade(trade);
}
return mmTrades.Count;
}
Any ideas guys? Thanks!
The problem is your OnRefresh method. Because the return type is void the method is not awaited [Check out this answer]. In addition you dont even try to await for the method inside your delegate
Changing the method to the following:
private async Task OnRefresh()
{
try
{
var trades = await ExchangeServiceInstance.GetTrades("");
mmTrades = new ObservableCollection<EEtrade>(trades);
tradeListView.ItemsSource = mmTrades;
}
catch { }
}
And await this method inside your delegate, should solve your problem:
public async void OnSignalReceived()
{
// THIS NEEDS TO FINISH FIRST, BUT IT DOESN'T
await tradeListView.Dispatcher.InvokeAsync((Action)async delegate
{
if (ExchangeServiceInstance.SelectedTabIndex == CURRENT_TAB_INDEX_ITEM)
{
await Task.Delay(MMConfig.DELAYMILLISEC);
await OnRefresh();
}
});
// SOMEHOW THIS GETS CALLED FIRST BEFORE THE ABOVE GETS TO FINISH!
await OnSaveTrades();
}
The use of (Action)async is basically the same as async void, and async void is almost always a mistake. Specifically, the consumer cannot know the outcome (unless it faults synchronously). The dispatcher here isn't really thinking of async.
If we assume that you must use the dispatcher here, perhaps a workaround might be to use something like a SemaphoreSlim (or maybe a TaskCompletionSource<something>) that you signal at the end of your async work (even in the exception case), and then await that; untested, but:
var tcs = new TaskCompletionSource<bool>();
await tradeListView.Dispatcher.InvokeAsync((Action)async delegate
{
try {
if (ExchangeServiceInstance.SelectedTabIndex == CURRENT_TAB_INDEX_ITEM)
{
await Task.Delay(MMConfig.DELAYMILLISEC);
OnRefresh();
}
tcs.TrySetResult(true);
} catch (Exception ex) {
tcs.TrySetException(ex);
}
});
await tcs.Task; // ensure the async work is complete
await OnSaveTrades();
First of all, you are using the async void pattern a lot. This is really bad practice for a number of reasons. You should stop doing that.
The problem here is that OnRefresh is again an async void method that can't be awaited but should be:
private async Task OnRefresh()
{
try
{
var trades = await ExchangeServiceInstance.GetTrades("");
mmTrades = new ObservableCollection<EEtrade>(trades);
tradeListView.ItemsSource = mmTrades;
}
catch { }
}
In your OnSignalReceived method change the call to OnRefresh(); to await OnRefresh();

Factory for IAsyncEnumerable or IAsyncEnumerator

I'm wondering if there is a way to create either IAsyncEnumerable<T> or IAsyncEnumerator<T> via a Source object, rather like TaskCompletionSource allows one to do for tasks. In particular, TaskCompletionSource can be passed around like any other parameter.
Maybe something like this:
public class AsyncEnumerables {
public Task HandlerTask { get; set; }
public async Task<string> ParentMethod() {
var source = new AsyncEnumerableSource<int>();
IAsyncEnumerable asyncEnumerable = source.GetAsyncEnumerable();
HandlerTask = Task.Run(() => handleAsyncResultsAsTheyHappen(asyncEnumerable));
int n = await someOtherTask();
source.YieldReturn(n);
var r = await ChildMethod(source);
source.Complete(); // this call would cause the HandlerTask to complete.
return r;
}
private async Task<string> ChildMethod(AsyncEnumerableSource<int> source) {
source.YieldReturn(5);
await SomeOtherCall();
source.YieldReturn(10);
return "hello";
}
}
With the above code, the handleAsyncResultsAsTheyHappen task would see whatever values got passed into YieldReturn. So it would see the n from the above code, as well as the 5 and the 10 from ChildMethod.
Here is another implementation of the AsyncEnumerableSource class, that doesn't depend on the Rx library. This one depends instead on the Channel<T>, class, which is natively available in the .NET standard libraries. It has identical behavior to the Rx-based implementation.
The class AsyncEnumerableSource can propagate notifications to multiple subscribers. Each subscriber can enumerate these notifications at its own pace. This is possible because each subscription has its own dedicated Channel<T> as underlying storage. The lifetime of a subscription is practically tied to the lifetime of a single await foreach loop. Breaking early from a loop for any reason (including thrown exceptions), ends immediately the subscription.
In technical terms a new subscription is created the first time that the MoveNextAsync method of an IAsyncEnumerator<T> is invoked. Calling the method GetAsyncEnumerable alone doesn't create a subscription, nor calling the GetAsyncEnumerator method does. The subscription ends when the associated IAsyncEnumerator<T> is disposed.
public class AsyncEnumerableSource<T>
{
private readonly List<Channel<T>> _channels = new();
private bool _completed;
private Exception _exception;
public async IAsyncEnumerable<T> GetAsyncEnumerable(
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
Channel<T> channel;
lock (_channels)
{
if (_exception != null) throw _exception;
if (_completed) yield break;
channel = Channel.CreateUnbounded<T>(
new() { SingleWriter = true, SingleReader = true });
_channels.Add(channel);
}
try
{
await foreach (var item in channel.Reader.ReadAllAsync()
.WithCancellation(cancellationToken).ConfigureAwait(false))
{
yield return item;
cancellationToken.ThrowIfCancellationRequested();
}
}
finally { lock (_channels) _channels.Remove(channel); }
}
public void YieldReturn(T value)
{
lock (_channels)
{
if (_completed) return;
foreach (var channel in _channels) channel.Writer.TryWrite(value);
}
}
public void Complete()
{
lock (_channels)
{
if (_completed) return;
foreach (var channel in _channels) channel.Writer.TryComplete();
_completed = true;
}
}
public void Fault(Exception error)
{
lock (_channels)
{
if (_completed) return;
foreach (var channel in _channels) channel.Writer.TryComplete(error);
_completed = true;
_exception = error;
}
}
}
The reason for the cancellationToken.ThrowIfCancellationRequested(); is because of this issue: ChannelReader.ReadAllAsync(CancellationToken) not actually cancelled mid-iteration.
Caution: in case you start propagating values with YieldReturn before any consumer has subscribed to the AsyncEnumerableSource, these values are going to be lost. No subscriber is going to observe them. To prevent this scenario you should make sure that all consumers have subscribed before starting the producers. The easiest way to do it is for the consumers to be async methods, with the await foreach being the first await inside the async method:
// Correct, synchronous subscription
async Task Consume()
{
await foreach (var item in source.GetAsyncEnumerable())
{
//...
}
}
Task consumer = Consume();
Avoid the temptation to use the Task.Run method, because in this case the subscription will occur asynchronously on a ThreadPool thread, and not synchronously with the creation of the consumer:
// Wrong, delayed subscription (possibility for unobserved values)
Task consumer = Task.Run(async () =>
{
await foreach (var item in source.GetAsyncEnumerable())
{
//...
}
});
In case that you don't want to do the subscriptions synchronously, it is possible to offload them to the ThreadPool, and await them to be established before starting the producers:
// Correct, awaited subscription
Task consumer = await Task.Factory.StartNew(async () =>
{
HeavySynchronousComputation();
await foreach (var item in source.GetAsyncEnumerable())
{
//...
}
}, default, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
The Task.Factory.StartNew(async method creates a nested Task<Task>. The outer task represents the subscription, and the inner task represents the consuming loop.
You're much better off if you can structure your code to take advantage of yield return and await foreach. E.g., this code does almost the same thing:
public async Task Consume()
{
var source = ParentMethod();
HandlerTask = Task.Run(async () => { await foreach (var item in source) { Console.WriteLine(item); } });
}
public async IAsyncEnumerable<int> ParentMethod()
{
await Task.Yield();
yield return 13;
await foreach (var item in ChildMethod())
yield return item;
}
private async IAsyncEnumerable<int> ChildMethod()
{
yield return 5;
await Task.Yield();
yield return 10;
}
However, if you really need an "async enumerable source", you need to first recognize one thing. TaskCompletionSource<T> holds the results, i.e., the T (or exception). It's acting as a container. The result can be set before the task is awaited. It's the same thing with the "async enumerable source" - you'd need it to be able to hold results before any items are taken from it. The "async enumerable source" would need to hold multiple results - in this case, a collection.
So what you're actually asking for is "a collection that can be consumed as an asynchronous enumerable". There are a few possibilities here, but the one I'd recommend is a Channel:
public async Task<string> ParentMethod()
{
var source = Channel.CreateUnbounded<int>();
var sourceWriter = source.Writer;
IAsyncEnumerable<int> asyncEnumerable = source.Reader.ReadAllAsync();
HandlerTask = Task.Run(async () => { await foreach (var item in asyncEnumerable) Console.WriteLine(item); });
await Task.Yield();
await sourceWriter.WriteAsync(13);
var r = await ChildMethod(sourceWriter);
sourceWriter.Complete();
return r;
}
private async Task<string> ChildMethod(ChannelWriter<int> sourceWriter)
{
await sourceWriter.WriteAsync(5);
await Task.Yield();
await sourceWriter.WriteAsync(10);
return "hello";
}
AFAIK the .NET platform has no built-in AsyncEnumerableSource class, but it is easy to implement one by using the System.Reactive and System.Linq.Async libraries. The System.Reactive library contains the class Subject which is a combination of an IObservable and IObserver. This is a convenient class, because you can send notifications to the IObserver interface, and subscribe independently any number of times to the IObservable interface to receive these notifications back. Actually it's not required to subscribe manually, because the System.Linq.Async library contains the handy extension method ToAsyncEnumerable, that converts an IObservable to IAsyncEnumerable automatically.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
public class AsyncEnumerableSource<T>
{
private readonly Subject<T> _subject = new Subject<T>();
public IAsyncEnumerable<T> GetAsyncEnumerable() => _subject.ToAsyncEnumerable();
public void YieldReturn(T value) => _subject.OnNext(value);
public void Complete() => _subject.OnCompleted();
public void Fault(Exception ex) => _subject.OnError(ex);
}
This implementation will send to the subscribers only the notifications that occurred after their subscription. If you want to ensure that late joiners will get the early messages, you could replace the Subject with a ReplaySubject. This one buffers the notifications it receives, so it comes with memory usage considerations: it accepts an int bufferSize argument in its constructor.
Note: The above implementation is thread-safe, although the Subject<T> class is not synchronized, and in general calling OnNext from multiple threads in parallel breaks the Rx contract. That's because the ToAsyncEnumerable operator does not depend on the Rx contract for its correctness, and synchronizes the incoming notifications. It's not a particularly efficient implementation though. Channel<T>-based implementations are significantly more efficient under heavy load.

Do Multi delegate Async Actions Only work with Begin Invoke

I'm trying to understand async actions and I'm a bit confused.
Actions are just glorified Delegates. Given the Actions
Action act = null;
act += () => { Console.WriteLine("Sync"); };
act += async () => { await File.AppendAllLinesAsync("C:/Test.Txt",
new[] { "Async File Operation" });
};
How can we invoke this async seeing as one of the delegates is async and the other is not. I've seen some extension methods in other SO answers simplified for the example would look like so:
public static void InvokeAsync(this Action action, AsyncCallback ar, object userObject = null)
{
var listeners = action.GetInvocationList();
foreach (var t in listeners)
{
var handler = (Action)t;
handler.BeginInvoke(ar, userObject);
}
}
I'm concerned if this even works because it looks like it invokes your callback for each listener which doesn't make sense.
I've only been using async with the more friendly version async/await so I do not understand this syntax as much. (I'm assuming the callback would be everything after the await and the userObject is equivalent to the dreadful SyncronizationContext that causes deadlocks if when calling sync without ConfigureAwait(false), but that is just a guess)
This is syntax inconvenient so I would perfer to use async await syntax, since async/await is called using duck-typing. I've read a blog about using async with delegates which for the example
public static class DelegateExtensions
{
public static TaskAwaiter GetAwaiter(this Action action)
{
Task task = new Task(action);
task.Start();
return task.GetAwaiter();
}
}
This too concerns me for a few reason, this looks much like an anti pattern.
Isn't this just creating a task which will run my action synchronous on a seperate thread? I also don't see this run through the invocation list.
Are either of these methods proper for invoking run delegates asynchronously?
Is there a way I can invoke an async delegate with the await syntax while still fully leveraging async?
What is the proper way to invoke async delegates with multiple functions in the invocation list?
I think Eric Lippert's comment have clarified the situation more than I could ever.
Overall, if you need to act on the return type of a method, you shouldn't use multicast delegates. If you still have to, at least use a Func<Task> signature, then you can iterate on each individual delegate using GetInvocationList, as explained here.
But would it be really impossible to work your way out of a multicast delegate with async void method?
It turns out that you can be notified of beginning and end of async void methods by using a custom synchronization context and overriding the OperationStarted and OperationCompleted methods. We can also override the Post method to set the synchronization context of child operations, to capture subsequent async void calls.
Piecing it together, you could come with something like:
class Program
{
static async Task Main(string[] args)
{
Action act = null;
act += () => { Console.WriteLine("Sync"); };
act += async () =>
{
Callback();
await Task.Delay(1000);
Console.WriteLine("Async");
};
await AwaitAction(act);
Console.WriteLine("Done");
Console.ReadLine();
}
static async void Callback()
{
await Task.Delay(2000);
Console.WriteLine("Async2");
}
static Task AwaitAction(Action action)
{
var delegates = action.GetInvocationList();
var oldSynchronizationContext = SynchronizationContext.Current;
var asyncVoidSynchronizationContext = new AsyncVoidSynchronizationContext();
try
{
SynchronizationContext.SetSynchronizationContext(asyncVoidSynchronizationContext);
var tasks = new Task[delegates.Length];
for (int i = 0; i < delegates.Length; i++)
{
((Action)delegates[i]).Invoke();
tasks[i] = asyncVoidSynchronizationContext.GetTaskForLastOperation();
}
return Task.WhenAll(tasks);
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext);
}
}
}
public class AsyncVoidSynchronizationContext : SynchronizationContext
{
private TaskCompletionSource<object> _tcs;
private Task _latestTask;
private int _operationCount;
public Task GetTaskForLastOperation()
{
if (_latestTask != null)
{
var task = _latestTask;
_latestTask = null;
return task;
}
return Task.CompletedTask;
}
public override void Post(SendOrPostCallback d, object state)
{
Task.Run(() =>
{
SynchronizationContext.SetSynchronizationContext(this);
d(state);
});
}
public override void OperationStarted()
{
if (Interlocked.Increment(ref _operationCount) == 1)
{
// First operation
_tcs = new TaskCompletionSource<object>();
_latestTask = _tcs.Task;
}
base.OperationStarted();
}
public override void OperationCompleted()
{
if (Interlocked.Decrement(ref _operationCount) == 0)
{
// Last operation
_tcs.TrySetResult(null);
}
base.OperationCompleted();
}
}
The output would be:
Sync
Async
Async2
Done
Of course, this code is provided just for recreational purpose. There's plenty of limitations, such as the fact the fact that it wouldn't work as-is if you're already using a synchronization context (such as the WPF one). I'm also certain that it has a few subtle bugs and concurrency issues here and there.

await not blocking until Task finishes

I am trying to block RequestHandler.ParseAll() with await ConsumerTask;, but when i set a breakpoint there, i ALWAYS get the "Done..." output first... and then Parse2() fails with a NullReferenceException. (thats my guess: "the GC starts cleaning up because _handler got out of scope")
Anyway, I can't figure out why that happens.
class MainClass
{
public async void DoWork()
{
RequestHandler _handler = new RequestHandler();
string[] mUrls;
/* fill mUrls here with values */
await Task.Run(() => _handler.ParseSpecific(mUrls));
Console.WriteLine("Done...");
}
}
static class Parser
{
public static async Task<IEnumerable<string>> QueryWebPage(string url) { /*Query the url*/ }
public static async Task Parse1(Query query)
{
Parallel.ForEach(/*Process data here*/);
}
public static async Task Parse2(Query query)
{
foreach(string line in query.WebPage)
/* Here i get a NullReference exception because query.WebPage == null */
}
}
sealed class RequestHandler
{
private BlockingCollection<Query> Queue;
private Task ConsumerTask = Task.Run(() => /* call consume() for each elem in the queue*/);
private async void Consume(Query obj)
{
await (obj.BoolField ? Parser.Parse1(obj) : Parser.Parse2(obj));
}
public async void ParseSpecific(string[] urls)
{
foreach(string v in urls)
Queue.Add(new Query(await QueryWebPage(v), BoolField: false));
Queue.CompleteAdding();
await ConsumerTask;
await ParseAll(true);
}
private async Task ParseAll(bool onlySome)
{
ReInit();
Parallel.ForEach(mCollection, v => Queue.Add(new Query(url, BoolField:false)));
Queue.CompleteAdding();
await ConsumerTask;
/* Process stuff further */
}
}
struct Query
{
public readonly string[] WebPage;
public readonly bool BoolField;
public Query(uint e, IEnumerable<string> page, bool b) : this()
{
Webpage = page.ToArray();
BoolField = b;
}
}
CodesInChaos has spotted the problem in comments. It stems from having async methods returning void, which you should almost never do - it means you've got no way to track them.
Instead, if your async methods don't have any actual value to return, you should just make them return Task.
What's happening is that ParseSpecific is only running synchronously until the first await QueryWebPage(v) that doesn't complete immediately. It's then returning... so the task started here:
await Task.Run(() => _handler.ParseSpecific(mUrls));
... completes immediately, and "Done" gets printed.
Once you've made all your async methods return Task, you can await them. You also won't need Task.Run at all. So you'd have:
public async void DoWork()
{
RequestHandler _handler = new RequestHandler();
string[] mUrls;
await _handler.ParseSpecific(mUrls);
Console.WriteLine("Done...");
}
...
public async TaskParseSpecific(string[] urls)
{
foreach(string v in urls)
{
// Refactored for readability, although I'm not sure it really
// makes sense now that it's clearer! Are you sure this is what
// you want?
var page = await QueryWebPage(v);
Queue.Add(new Query(page, false);
}
Queue.CompleteAdding();
await ConsumerTask;
await ParseAll(true);
}
Your Reinit method also needs changing, as currently the ConsumerTask will basically complete almost immediately, as Consume will return immediately as it's another async method returning void.
To be honest, what you've got looks very complex, without a proper understanding of async/await. I would read up more on async/await and then probably start from scratch. I strongly suspect you can make this much, much simpler. You might also want to read up on TPL Dataflow which is designed to make producer/consumer scenarios simpler.

How to work with HttpTaskAsyncHandler

public class FooHandler : HttpTaskAsyncHandler
{
public override async Task ProcessRequestAsync(HttpContext context)
{
return await new AdRequest().ProcessRequest();
// getting error here. "Return type of async type is void"
}
}
public class FooRequest
{
public async Task<String> ProcessRequest()
{
//return await "foo"; obviously nothing to wait here
}
}
I want to make a async handler and just want to return a string. How can i get this working? and is there a concise reference to work with Async methods and Tasks?
A few points:
You can await any Task, not just ones returned from async methods.
async methods wrap their returned value into a Task<TResult>; if there is no return value, they wrap the return itself into a Task.
There are several convenience methods available, e.g., Task.FromResult, if you don't need the overhead of an async method.
Only make a method async if you have to use await in it. If you don't need to make the method async, don't.
You may find my async/await intro helpful.
public class FooHandler : HttpTaskAsyncHandler
{
public override Task ProcessRequestAsync(HttpContext context)
{
return new AdRequest().ProcessRequest();
}
}
public class AdRequest
{
public Task<String> ProcessRequest()
{
return Task.FromResult("foo");
}
}
You shouldn't "return" the Task, the compiler will do it implicitly as it is an async function:
public override async Task ProcessRequestAsync(HttpContext context)
{
await new AdRequest().ProcessRequest();
}
public async Task<String> ProcessRequest()
{
return "foo";
}
This is another way, closer to what you were trying to do: (without async/await)
public override Task ProcessRequestAsync(HttpContext context)
{
return new AdRequest().ProcessRequest();
}
public Task<String> ProcessRequest()
{
return Task.Return("foo");
}
A general reference to async is here
Essentially adding the async modifier to a method, makes it return a Task implicitly. If you return an int, it will turn it into a Task<int>. await does the opposite, turning a Task<int> into an int.
This is a truly asynchronous method:
public Task<string> ProcessRequest()
{
var textFile = File.OpenText("file.txt");
var readTask = textFile.ReadToEndAsync();
readTask.ContinueWith(previousTask => textFile.Dispose());
return readTask;
}
If you run this method with a large file or a file on a slow drive the execution will return to caller long before file reading ends. In Stephen Cleary's example the caller will get back control only when the result ("foo") is finished calculating.
Dispose must be in ContinueWith because the method execution will return to caller before file reading is complete so file can't be closed in ProcessRequest method.
One can of course start their own task.
public Task<string> ProcessRequest(CancellationToken cancellationToken)
{
var readTask = Task.Run(() =>
{
using (var textFile = File.OpenText("file.txt"))
{
var text = textFile.ReadToEnd();
cancellationToken.ThrowIfCancellationRequested();
var processedText = text.Replace("foo", "bar");
return processedText;
}
});
return readTask;
}
It is a good practice to have a CancellationToken and periodically check if cancellation was requested to allow long running operarions to be cancelled.
Edit 1
As #Stephen Cleary highlighted the first sample and this result in approximately or maybe exactly the same CIL:
public async Task<string> ProcessRequest()
{
using (var textFile = File.OpenText("file.txt"))
{
var s = await textFile.ReadToEndAsync();
return s;
}
}
Basically the compiler will transform the code following await textFile.ReadToEndAsync() into ContinueWith.
Each syntax has its benefits, my preference is that 1-2 lines (i.e. dispose and log) go into ContinueWith, more complex continuation uses await.

Categories