Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Async Function Extensibility

As for extensibility, if you can wrap a Task object around an operation that completes in the future, you can use the await operator to await that operation. Having a single type (Task) to represent all kinds of asynchronous operations is phenomenally useful because it allows you to implement combinators (like Task’s WhenAll and WhenAny methods) and other helpful operations. Later in this

chapter, I demonstrate doing this by wrapping a CancellationToken with a Task so I can await an asynchronous operation while also exposing timeout and cancellation.

I’d also like to share with you another example. The following is my TaskLogger class, which you can use to show you asynchronous operations that haven’t yet completed. This is very useful in

debugging scenarios especially when your application appears hung due to a bad request or a non- responding server.

 

public static class TaskLogger {

public enum TaskLogLevel { None, Pending }

public static TaskLogLevel LogLevel { get; set; }

 

public sealed class TaskLogEntry {

public Task Task { get; internal set; }


public String Tag { get; internal set; } public DateTime LogTime { get; internal set; }

public String CallerMemberName { get; internal set; } public String CallerFilePath { get; internal set; } public Int32 CallerLineNumber { get; internal set; } public override string ToString() {

return String.Format("LogTime={0}, Tag={1}, Member={2}, File={3}({4})",

LogTime, Tag ?? "(none)", CallerMemberName, CallerFilePath, CallerLineNumber);

}

}

 

 

private static readonly ConcurrentDictionary<Task, TaskLogEntry> s_log = new ConcurrentDictionary<Task, TaskLogEntry>();

public static IEnumerable<TaskLogEntry> GetLogEntries() { return s_log.Values; }

 

public static Task<TResult> Log<TResult>(this Task<TResult> task, String tag = null, [CallerMemberName] String callerMemberName = null,

[CallerFilePath] String callerFilePath = null, [CallerLineNumber] Int32 callerLineNumber = ­1) { return (Task<TResult>)

Log((Task)task, tag, callerMemberName, callerFilePath, callerLineNumber);

}

 

public static Task Log(this Task task, String tag = null, [CallerMemberName] String callerMemberName = null, [CallerFilePath] String callerFilePath = null, [CallerLineNumber] Int32 callerLineNumber = ­1) {

if (LogLevel == TaskLogLevel.None) return task; var logEntry = new TaskLogEntry {

Task = task,

LogTime = DateTime.Now, Tag = tag,

CallerMemberName = callerMemberName, CallerFilePath = callerFilePath, CallerLineNumber = callerLineNumber

};

s_log[task] = logEntry;

task.ContinueWith(t => { TaskLogEntry entry; s_log.TryRemove(t, out entry); }, TaskContinuationOptions.ExecuteSynchronously);

return task;

}

}

 

And here is some code that demonstrates the use of the class.

 

public static async Task Go() {

#if DEBUG

// Using TaskLogger incurs a memory and performance hit; so turn it on in debug builds TaskLogger.LogLevel = TaskLogger.TaskLogLevel.Pending;



#endif

 

// Initiate 3 task; for testing the TaskLogger, we control their duration explicitly var tasks = new List<Task> {

Task.Delay(2000).Log("2s op"),

Task.Delay(5000).Log("5s op"),


Task.Delay(6000).Log("6s op")

};

 

try {

// Wait for all tasks but cancel after 3 seconds; only 1 task should complete in time

// Note: WithCancellation is my extension method described later in this chapter await Task.WhenAll(tasks).

WithCancellation(new CancellationTokenSource(3000).Token);

}

catch (OperationCanceledException) { }

 

// Ask the logger which tasks have not yet completed and sort

// them in order from the one that’s been waiting the longest

foreach (var op in TaskLogger.GetLogEntries().OrderBy(tle => tle.LogTime)) Console.WriteLine(op);

}

 

When I build and run this code, I get the following output.

 

LogTime=7/16/2012 6:44:31 AM, Tag=6s op, Member=Go, File=C:\CLR via C#\Code\Ch28­1­IOOps.cs(332) LogTime=7/16/2012 6:44:31 AM, Tag=5s op, Member=Go, File=C:\CLR via C#\Code\Ch28­1­IOOps.cs(331)

 

In addition to all the flexibility you have with using Task, async functions have another extensibil- ity point: the compiler calls GetAwaiter on whatever operand is used with await. So, the operand doesn’t have to be a Task object at all; it can be of any type as long as it has a GetAwaiter method available to call. Here is an example of my own awaiter that is the glue between an async method’s state machine and an event being raised.

 

public sealed class EventAwaiter<TEventArgs> : INotifyCompletion {

private ConcurrentQueue<TEventArgs> m_events = new ConcurrentQueue<TEventArgs>(); private Action m_continuation;

 

#region Members invoked by the state machine

// The state machine will call this first to get our awaiter; we return ourself public EventAwaiter<TEventArgs> GetAwaiter() { return this; }

 

// Tell state machine if any events have happened yet

public Boolean IsCompleted { get { return m_events.Count > 0; } }

 

// The state machine tells us what method to invoke later; we save it public void OnCompleted(Action continuation) {

Volatile.Write(ref m_continuation, continuation);

}

 

// The state machine queries the result; this is the await operator's result public TEventArgs GetResult() {

TEventArgs e; m_events.TryDequeue(out e); return e;

}

#endregion

 

// Potentially invoked by multiple threads simultaneously when each raises the event


public void EventRaised(Object sender, TEventArgs eventArgs) { m_events.Enqueue(eventArgs); // Save EventArgs to return it from GetResult/await

 

// If there is a pending continuation, this thread takes it

Action continuation = Interlocked.Exchange(ref m_continuation, null); if (continuation != null) continuation(); // Resume the state machine

}

}

 

And here is a method that uses my EventAwaiter class to return from an await operator when- ever an event is raised. In this case, the state machine continues whenever any thread in the App- Domain throws an exception.

 

private static async void ShowExceptions() {

var eventAwaiter = new EventAwaiter<FirstChanceExceptionEventArgs>(); AppDomain.CurrentDomain.FirstChanceException += eventAwaiter.EventRaised;

 

while (true) {

Console.WriteLine("AppDomain exception: {0}", (await eventAwaiter).Exception.GetType());

}

}

 

And finally, here is some code that demonstrates it all working.

 

public static void Go() { ShowExceptions();

 

for (Int32 x = 0; x < 3; x++) { try {

switch (x) {

case 0: throw new InvalidOperationException(); case 1: throw new ObjectDisposedException(""); case 2: throw new ArgumentOutOfRangeException();

}

}

catch { }

}

}

 

 


Date: 2016-03-03; view: 910


<== previous page | next page ==>
Nbsp;   How the Compiler Transforms an Async Function into a State Machine | Nbsp;   Async Functions in the Framework Class Library
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.012 sec.)