I'm facing difficulties understanding how to handle program control during asynchronous flow.
I have a SessionManager class which calls the initiates the session and we need to register
for the event OnStartApplicationSessionResponse and my control will return to the calling point. I will get the session id in the eventhandler after sometime or the error code if there is an error.
class SessionManager
{
public bool startUp(Object params)
{
try
{
serviceProvider = new ServiceProvider();
serviceProvider.OnStartApplicationSessionResponse += new StartApplicationSessionResponseHandler(ServiceProvider_OnStartApplicationSessionResponse);
serviceProvider.startUp(params);
}
}
public void ServiceProvider_OnStartApplicationSessionResponse(object sender, ServiceProvider.StartApplicationSessionResponseArgs e)
{
//e.getError
//I will get the session Id here or error code
}
}
How do I get sessionId or the error as my control is now at the calling position?
You could use TaskCompletionSource to make the Event awaitable.
class SessionManager
{
private ServiceProvider _serviceProvider;
public int SessionId
{
get;
private set;
}
public Task<bool> StartUp(Object param)
{
_serviceProvider = new ServiceProvider();
var tcs = new TaskCompletionSource<bool>();
_serviceProvider.OnStartApplicationSessionResponse += (sender, args) =>
{
// do your stuff
// e.g.
SessionId = 0xB00B5;
tcs.SetResult(true);
};
_serviceProvider.startUp(param);
return tcs.Task;
}
}
The call would look like:
private static async void SomeButtonClick()
{
var mgr = new SessionManager();
var success = await mgr.StartUp("string");
if (success)
{
Console.WriteLine(mgr.SessionId);
// update ui or whatever
}
}
note: This Feature is available in .Net 4.5.
With the C# feature async and await you are able to rewrite an asynchronous flow into something that is like a synchronous flow. You have only provided some fragments of your code so to provide a complete example I have created some code that resembles your code:
class StartEventArgs : EventArgs {
public StartEventArgs(Int32 sessionId, Int32 errorCode) {
SessionId = sessionId;
ErrorCode = errorCode;
}
public Int32 SessionId { get; private set; }
public Int32 ErrorCode { get; private set; }
}
delegate void StartEventHandler(Object sender, StartEventArgs e);
class ServiceProvider {
public event StartEventHandler Start;
public void Startup(Boolean succeed) {
Thread.Sleep(TimeSpan.FromSeconds(1));
if (succeed)
OnStart(new StartEventArgs(321, 0));
else
OnStart(new StartEventArgs(0, 123));
}
protected void OnStart(StartEventArgs e) {
var handler = Start;
if (handler != null)
handler(this, e);
}
}
The ServiceProvider.Startup method will delay for a second before firing an event that either signals success or failure depending on the succeed parameter provided. The method is rather silly but hopefully is similar to the behavior of your ServiceProvider.Startup method.
You can convert the asynchronous startup into a task using a TaskCompletionSource:
Task<Int32> PerformStartup(ServiceProvider serviceProvider, Boolean succeed) {
var taskCompletionSource = new TaskCompletionSource<Int32>();
serviceProvider.Start += (sender, e) => {
if (e.ErrorCode > 0)
throw new Exception(e.ErrorCode.ToString());
taskCompletionSource.SetResult(e.SessionId);
};
serviceProvider.Startup(succeed);
return taskCompletionSource.Task;
}
Notice how an error signaled by the Start event is converted into an Exception (in production code you should use a custom exception type instead).
Using the async and await feature of C# you can now write code that looks very much like synchronous code even though it actually is asynchronous:
async void Startup(Boolean succeed) {
var serviceProvider = new ServiceProvider();
try {
var sessionId = await PerformStartup(serviceProvider, succeed);
Console.WriteLine(sessionId);
}
catch (Exception ex) {
Console.WriteLine(ex);
}
}
If an error is reported by the Start event you can now deal with in the catch block. Also the session ID is simply a return value of the function. The "magic" is that using await on a Task will return the result of the task when it completes and if an exception is thrown in the task it can be caught on the thread awaiting the task.
Related
I have a situation where there is a synchronous call to a method and based on a parameter we switch the response for sync /async and inside async we start a task as below. The problem is tracer works until its outside the Task() but not inside. Is it happening that Task cannot access the parent thread data ?
RequestProcessor.cs file code:
public classA_Res GetListbyUserRequest(ApiRequest<object> request)
{
if(request.IsAsync)
{
LocalTracer.TraceInformation(AVEventType.Info, "Async call started"); // this works/ gets logged in db
var taskProcessEventsResponseAsync = new Task(() =>
ProcessResponseAsync(validatedInputs, options, grids, userInfo, traceRequest, exportAs, userRequestId, sessionId));
taskProcessEventsResponseAsync.Start();
}
else
{
response=DataManager.Instance.GetListbyUserRequest(); // this gets paginated data for UI
}
//some code for response that request has been put down for export async.
}
private void ProcessResponseAsync(validatedInputs, options, grids, userInfo, traceRequest, exportAs, userRequestId, sessionId)
{
LocalTracer.TraceInformation(AVEventType.Info, "Async call in progress"); // this doesnt works/ doesnt gets logged in db but also doesnt throws any error
//some code for processing data in chunks and creating files on server
}
LocalTracer.cs
public interface ILocalTracer
{
void TraceInformation(AVEventType eventType, DummyParameter dummy = null);
}
public sealed class LocalTracer:ILocalTracer
{
static ILocalTracer _instance = new LocalTracer();
public static ILocalTracer Instance
{
get { return _instance; }
set { _instance = value; }
}
private LocalTracer()
{
}
public static void TraceInformation(AVEventType eventType, string sMessage = "", string
userName = "", string ipAddress = "",.....)
{
//tracer code
}
public void TraceInformation(AVEventType eventType, DummyParameter dummy = null)
{
TraceInformation(eventType, "");
}
}
Please assume all this code in proper try catch blocks.
I am working with Azure Event Hub producer client and reading messages off of a kafka stream then pass it along to deserialize/map, then pass to Event Hub. I have the consume loop which is creating a task for each consume and then two methods to do processing(this seems to have greatly improved the speed from the kafka lag perspective. However, Event hub makes you create an event batch which I don't necessarily want to use. I just want to send the data one message at a time for now. In order to create a new batch I have to call Dispose(). I am running into an issue where there's another call to the function by the the time I call Dispose() and I get an error saying the object is being used by event hub.
I've also tried using the overload for eventHubProducerClient.SendAsync that allows you to pass in a IEnumerable but i'm running into the same issue with that.
So I believe this to be a synchronization issue, or maybe I need to do a lock somewhere?
Any help would be appreciated.
public void Execute()
{
using (_consumer)
{
try
{
_consumer.Subscribe(_streamConsumerSettings.Topic);
while (true)
{
var result = _consumer.Consume(1000);
if (result == null)
{
continue;
}
var process = Task.Factory.StartNew(() => ProcessMessage(result?.Message?.Value));
var send = process.ContinueWith(t => SendMessage(process.Result));
}
}
catch (ConsumeException e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
_cancelConsume = true;
_consumer.Close();
RestartConsumer();
}
}
}
public static EquipmentJson ProcessMessage(byte[] result)
{
var json = _messageProcessor.DeserializeAndMap(result);
return json;
}
public static void SendMessage(EquipmentJson message)
{
try
{
_eventHubClient.AddToBatch(message);
}
catch (Exception e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
}
}
public async Task AddToBatch(EquipmentJson message)
{
if
(!string.IsNullOrEmpty(message.EquipmentLocation))
{
try
{
var batch = await _equipmentLocClient.CreateBatchAsync();
batch.TryAdd(new EventData(Encoding.UTF8.GetBytes(message.EquipmentLocation)));
await _eventHubProducerClient.SendAsync(batch);
batch.Dispose();
_logger.LogInformation($"Data sent {DateTimeOffset.UtcNow}");
}
catch (Exception e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
}
}
}
public class EventHubClient : IEventHubClient
{
private readonly ILoggerAdapter<EventHubClient> _logger;
private readonly EventHubClientSettings _eventHubClientSettings;
private IMapper _mapper;
private static EventHubProducerClient _equipmentLocClient;
public EventHubClient(ILoggerAdapter<EventHubClient> logger, EventHubClientSettings eventHubClientSettings, IMapper mapper)
{
_logger = logger;
_eventHubClientSettings = eventHubClientSettings;
_mapper = mapper;
_equipmentLocClient = new EventHubProducerClient(_eventHubClientSettings.ConnectionString, _eventHubClientSettings.EquipmentLocation);
}
}
}
Based on my speculation in comments, I'm curious if refactoring to use async/await rather than the explicit continuation in the main loop may help. Perhaps something similar to the following LinqPad snippet:
async Task Main()
{
while (true)
{
var message = await Task.Factory.StartNew(() => GetText());
var events = new[] { new EventData(Encoding.UTF8.GetBytes(message)) };
await Send(events).ConfigureAwait(false);
}
}
public EventHubProducerClient client = new EventHubProducerClient("<< CONNECTION STRING >>");
public async Task Send(EventData[] events)
{
try
{
await client.SendAsync(events).ConfigureAwait(false);
"Sent".Dump();
}
catch (Exception ex)
{
ex.Dump();
}
}
public string GetText()
{
Thread.Sleep(250);
return "Test";
}
If you're set on keeping the continuation, I wonder if a slight structural refactoring in the continuation may help, both to push up creation of the events and to honor the await statements. Perhaps something similar to the following LinqPad snippet:
async Task Main()
{
while(true)
{
var t = Task.Factory.StartNew(() => GetText());
var _ = t.ContinueWith(async q =>
{
var events = new[] { new EventData(Encoding.UTF8.GetBytes(t.Result)) };
await Send(events).ConfigureAwait(false);
});
await Task.Yield();
}
}
public EventHubProducerClient client = new EventHubProducerClient("<< CONNECTION STRING >>");
public async Task Send(EventData[] events)
{
try
{
await client.SendAsync(events).ConfigureAwait(false);
"Sent".Dump();
}
catch (Exception ex)
{
ex.Dump();
}
}
public string GetText()
{
Thread.Sleep(250);
return "Test";
}
Problem Statement
I have a module code written in C# which waits for a topic called 'start' and as soon as it's received, then I need to send certain data periodically based om time interval. I need to accommodate my logic in such a way that 'start' topic can be called multiple times with different metadata value and all those has to run it's own instance with the same logic.
Issue I am Facing
Now the code always runs only one instance of 'SendPeriodicTelemetry' even though 'start' topic is invoked multiple times.
Code Snippet Sample
The below three classes are given with the order the call goes
public static async void MessageReceiveAsync(object sender, MqttApplicationMessageReceivedEventArgs e)
{
Console.WriteLine($"Message received on topic: '{e.ApplicationMessage.Topic}'. Payload: {msg}");
try
{
if (e.ApplicationMessage.Topic.Contains("methods"))
{
await PublisherClass.PublishToMethod(topicElement, msg);
}
}
}
public static class PublisherClass
{
public static bool StartInvoked { get; set; }
public static int StartCmdCount { get; set; } = 0;
public static async Task PublishToMethod(string methodName, string message)
{
try
{
string topic;
var sendPeriodicTelemetries = new SendPeriodicTelemetry[10];
switch (methodName)
{
case "stop":
for (int i = 0; i < StartCmdCount; i++)
{
Console.WriteLine($"start command index to stop: {i}");
sendPeriodicTelemetries[StartCmdCount].aTimer.Stop();
sendPeriodicTelemetries[StartCmdCount].aTimer.Dispose();
}
StartInvoked = false;
StartCmdCount = 0;
await CommonMethods.AckMethod(CommonMethods.listMessage, CommonMethods.requestId, MethodNames.sendTimeSeries);
break;
case "start":
sendPeriodicTelemetries[StartCmdCount] = new SendPeriodicTelemetry();
sendPeriodicTelemetries[StartCmdCount].TsAlarmAndEventTopicAndPayload(rawPayload, objId);
sendPeriodicTelemetries[StartCmdCount].aTimer = new System.Timers.Timer(timeIntervel);
sendPeriodicTelemetries[StartCmdCount].SetTimer(timeIntervel, sendPeriodicTelemetries[StartCmdCount].aTimer);
StartCmdCount++;
break;
}
}
catch(Exception exception)
{
Console.WriteLine($"Exception occurred: {exception.ToString()}");
}
}
}
public class SendPeriodicTelemetry
{
public void TsAlarmAndEventTopicAndPayload(JObject rawPayload, string objId)
{
}
public void SetTimer(int timeInterval, Timer timer)
{
if (!PublisherClass.StartInvoked)
{
PublisherClass.StartInvoked = true;
timer.Elapsed += SendPeriodic;
timer.AutoReset = true;
timer.Enabled = true;
}
}
public async void SendPeriodic(Object source, ElapsedEventArgs e)
{
Console.WriteLine("Starting to send periodic Telemetry at {0:HH:mm:ss.fff}", e.SignalTime);
await SendPeriodicTimeSeries(PublisherClass.TimeSeriesTopic, PublisherClass.TelemetrySingle, PublisherClass.Configuration);
await SendPeriodicAlarm(PublisherClass.AlarmTopic, PublisherClass.AlarmSingle);
await SendPeriodicEvent(PublisherClass.EventTopic, PublisherClass.EventSingle);
}
public async Task SendPeriodicTimeSeries(string topic, TelemetrySingle payload, JObject configuration)
{
}
public async Task SendPeriodicAlarm(string topic, AlarmSingle payload)
{
}
public async Task SendPeriodicEvent(string topic, EventSingle payload)
{
}
}
Scenario Flow
Consider from 'MessageReceiveAsync' I received a topic that contains a method(not C# method) called 'start' with some metadata for the first time then the 'PublishToMethod' is invoked with that method name ('start')
Then based on the metadata received inside 'start' case I initiate the timer to particular interval based on the metadata received along-with 'start' command.
Then all three below data will be sent on the set time intervals
await SendPeriodicTimeSeries(PublisherClass.TimeSeriesTopic,
PublisherClass.TelemetrySingle, PublisherClass.Configuration);
await SendPeriodicAlarm(PublisherClass.AlarmTopic, PublisherClass.AlarmSingle);
await SendPeriodicEvent(PublisherClass.EventTopic, PublisherClass.EventSingle);
Consider from 'MessageReceiveAsync' I received another topic that contains a method(not C# method) called 'start' again for the second time with a different metadata. It can hit any number of times.
Then again the specific timeinterval should be set for the timer and timer event has to go periodically for different kind of metadata
The previous periodic data should also go ahead as usual
Also whenever a 'stop' method is invoked the periodic timer events for all instances should stop.
Actual Result
As soon as I receive the 'start' for the second time the first invoked metadata values all switched to the second time invoked values and always the periodic data goes for the latest invoked value. Only one instance is running always. I want to send periodic data for as many instances as the number of times 'start' is invoked.
Expected Result
Multiple instances of timer event should get invoked and the below methods should keep sending the data according to the metadata received
await SendPeriodicTimeSeries(PublisherClass.TimeSeriesTopic,
PublisherClass.TelemetrySingle, PublisherClass.Configuration);
await SendPeriodicAlarm(PublisherClass.AlarmTopic, PublisherClass.AlarmSingle);
await SendPeriodicEvent(PublisherClass.EventTopic, PublisherClass.EventSingle);
There was a problem in Timer object initialization. It was still from a static class and hence changed that with the 'SendPeriodicTelemetry' instance level property. Then now I could able to invoke multiple instances with the specified time interval. And for stopping those time based events I need to dispose the Timer. That's it. Achieved my objective. And it's not required to initialize the
'SendPeriodicTelemetry' object as array to achieve that.
public static async void MessageReceiveAsync(object sender, MqttApplicationMessageReceivedEventArgs e)
{
Console.WriteLine($"Message received on topic: '{e.ApplicationMessage.Topic}'. Payload: {msg}");
try
{
if (e.ApplicationMessage.Topic.Contains("methods"))
{
await PublisherClass.PublishToMethod(topicElement, msg);
}
}
}
public static class PublisherClass
{
public static List<System.Timers.Timer> SendPeriodicTimerObjs { get; set; } = new List<System.Timers.Timer>();
public static async Task PublishToMethod(string methodName, string message)
{
try
{
switch (methodName)
{
case "start":
case "stop":
var sendPeriodicObj = new SendPeriodicTelemetry();
sendPeriodicObj.periodicTimer = new System.Timers.Timer();
SendPeriodicTimerObjs.Add(sendPeriodicObj.periodicTimer);
if (methodName == "start")
{
sendPeriodicObj.TsAlarmAndEventTopicAndPayload(rawPayload, objId);
sendPeriodicObj.periodicTimer.Interval = timeIntervel;
sendPeriodicObj.SetTimer(timeIntervel, sendPeriodicObj.periodicTimer);
}
else
{
sendPeriodicObj.periodicTimer.Stop();
foreach(var timer in SendPeriodicTimerObjs)
{
timer.Dispose();
}
SendPeriodicTimerObjs.Clear();
}
break;
}
}
catch(Exception exception)
{
Console.WriteLine($"Exception occurred: {exception.ToString()}");
}
}
}
public class SendPeriodicTelemetry
{
public System.Timers.Timer periodicTimer;
public void TsAlarmAndEventTopicAndPayload(JObject rawPayload, string objId)
{
}
public void SetTimer(int timeInterval, Timer timer)
{
Console.WriteLine($"Inside SetTimer method");
StartInvoked = true;
timer.Elapsed += SendPeriodic;
timer.AutoReset = true;
timer.Enabled = true;
}
public async void SendPeriodic(Object source, ElapsedEventArgs e)
{
Console.WriteLine("Starting to send periodic Telemetry at {0:HH:mm:ss.fff}", e.SignalTime);
await SendPeriodicTimeSeries(PublisherClass.TimeSeriesTopic, PublisherClass.TelemetrySingle, PublisherClass.Configuration);
await SendPeriodicAlarm(PublisherClass.AlarmTopic, PublisherClass.AlarmSingle);
await SendPeriodicEvent(PublisherClass.EventTopic, PublisherClass.EventSingle);
}
public async Task SendPeriodicTimeSeries(string topic, TelemetrySingle payload, JObject configuration)
{
}
public async Task SendPeriodicAlarm(string topic, AlarmSingle payload)
{
}
public async Task SendPeriodicEvent(string topic, EventSingle payload)
{
}
}
I have a problem, and I am not quite sure how to solve this, except for making my Akka Actor not have async methods.
Here is my Actor Code:
public class AggregatorActor : ActorBase, IWithUnboundedStash
{
public IStash Stash { get; set; }
private AggregatorTimer _aggregatorTimer;
private IActorSystemSettings _settings;
private AccountSummary _accountResponse;
private ContactDetails _contactResponse;
private AnalyticDetails _analyticsResponse;
private FinancialDetails _financialResponse;
private ActorSelection _accountActor;
private ActorSelection _contactActor;
private ActorSelection _analyticsActor;
private ActorSelection _financialActor;
public AggregatorActor(IActorSystemSettings settings) : base(settings)
{
_accountActor = Context.System.ActorSelection(ActorPaths.AccountActorPath);
_contactActor = Context.System.ActorSelection(ActorPaths.ContactActorPath);
_analyticsActor = Context.System.ActorSelection(ActorPaths.AnalyticsActorPath);
_financialActor = Context.System.ActorSelection(ActorPaths.FinancialActorPath);
_settings = settings;
}
#region Public Methods
public override void Listening()
{
ReceiveAsync<ProfilerMessages.ProfilerBase>(async x => await HandleMessageAsync(x));
}
private void Busy()
{
Receive<ProfilerMessages.ProfilerBase>(x => Stash.Stash());
}
private void Aggregate()
{
try
{
Context.Sender.Tell(AggregatedSummaryResponse.Instance(_accountResponse, _contactResponse, _analyticsResponse, _financialResponse));
}
catch (Exception ex)
{
ExceptionHandler(ex);
}
}
public override async Task HandleMessageAsync(object msg)
{
//if is summary, generate new isntance of AggregatorTimer in _eventHandlerCollection.
if (msg is ProfilerMessages.GetSummary)
{
//Become busy. Stash
Become(Busy);
//Handle different requests
var clientId = (msg as ProfilerMessages.GetSummary).ClientId;
await HandleSummaryRequest(clientId);
}
}
private async Task HandleSummaryRequest(string clientId)
{
try
{
var accountMsg = new AccountMessages.GetAggregatedData(clientId);
_accountResponse = (await _accountActor.Ask<Messages.AccountMessages.AccountResponseAll>(accountMsg, TimeSpan.FromSeconds(_settings.NumberOfSecondsToWaitForResponse))).AccountDetails;
//Need to uncomment this
var contactMsg = new ContactMessages.GetAggregatedContactDetails(clientId);
_contactResponse = (await _contactActor.Ask<Messages.ContactMessages.ContactResponse>(contactMsg, TimeSpan.FromSeconds(_settings.NumberOfSecondsToWaitForResponse))).ContactDetails;
var analyticMsg = new AnalyticsMessages.GetAggregatedAnalytics(clientId);
_analyticsResponse = (await _analyticsActor.Ask<Messages.AnalyticsMessages.AnalyticsResponse>(analyticMsg, TimeSpan.FromSeconds(_settings.NumberOfSecondsToWaitForResponse))).AnalyticDetails;
var financialMsg = new FinancialMessages.GetAggregatedFinancialDetails(clientId);
_financialResponse = (await _financialActor.Ask<Messages.FinancialMessages.FinancialResponse>(financialMsg, TimeSpan.FromSeconds(_settings.NumberOfSecondsToWaitForResponse))).FinancialDetails;
//Start new timer
_aggregatorTimer = new AggregatorTimer(_settings.NumberOfSecondsToWaitForResponse);
_aggregatorTimer.TimeElapsed += _aggregatorTimer_TimeElapsed;
}
catch (Exception ex)
{
ExceptionHandler(ex);
}
}
//Event that is raised when an external timers time elapsed.
private async void _aggregatorTimer_TimeElapsed(object sender, ElapsedTimeHandlerArg e)
{
Aggregate();
_aggregatorTimer = null;
_accountResponse = null;
_contactResponse = null;
_analyticsResponse = null;
_financialResponse = null;
//Unstash
Stash.Unstash();
//Start listening again
Become(Listening);
}
#endregion
}
Inside the _aggregatorTimer_TimeElapsed event, I call the await Aggregate function, but the following exception is thrown.
There is no active ActorContext, this is most likely due to use of async operations from within this actor
I think this is caused by the fact that the Aggregate function tries to Tell() the Sender about the responses that are aggregated, but those Tasksare not yet completed? I might be completely wrong, but I have no idea why this is thrown.
I'm not sure what do you even need an AggregatorTimer for - in akka you have a Context.System.Scheduler object, which can be used to schedule events going to happen in the future.
Another thing is that you probably shouldn't execute logic inside event handlers. If you really need them in your code, it's better to limit them only to send a message, once an event gets triggered i.e.:
Receive<TimedOut>(_ => /* handle timeout message */);
var self = Self; // bind current self to a variable, so it won't change
_aggregatorTimer.TimeElapsed += (sender, e) => self.Tell(new TimedOut(), self);
I'm creating a generic loader and I want to kick off an HttpClient SendAsync request. However, some of these requests may take time, so I want to add the option to cancel, and notify upon completion.
This seems like a standard scenario imho.
I'm not sure if this is the correct way to go about this, but based on some examples I've looked at, here is where I'm at. If you look at the bottom of the code, my question is - at that point, do I check the response and raise a success or error event?
public bool StartFetch()
{
if (IsFetching) return false;
IsFetching = true;
mCancellationTokenSource = new CancellationTokenSource();
// this is not awaited, so execution should continue
StartTask(request, mCancellationTokenSource.Token);
return true;
}
public bool CancelFetch()
{
// send cancellation
if (mCancellationTokenSource != null)
mCancellationTokenSource.Cancel();
Cleanup();
return true;
}
private async Task StartTask(LFHttpRequest request, CancellationToken cancellationToken)
{
var message = new HttpRequestMessage(request.Method, request.Uri);
var response = await HttpClient.SendAsync(message, cancellationToken);
// at this point, do I take a look at response and raise a custom OnSuccess or OnError event???
// or do I want to grab the task from `SendAsync`, check for completed or faulted?
}
When you're looking at exposing task-related state like IsFetching, it's often cleaner and easier to just expose the Task itself.
Something like this:
public Task<T> FetchTask { get; private set; }
public bool StartFetch()
{
if (FetchTask != null) return false;
mCancellationTokenSource = new CancellationTokenSource();
FetchTask = FetchAsync(request, mCancellationTokenSource.Token);
return true;
}
public bool CancelFetch()
{
// send cancellation
if (mCancellationTokenSource != null)
mCancellationTokenSource.Cancel();
FetchTask = null;
return true;
}
private async Task<T> FetchAsync(LFHttpRequest request, CancellationToken cancellationToken)
{
var message = new HttpRequestMessage(request.Method, request.Uri);
var response = await HttpClient.SendAsync(message, cancellationToken);
response.EnsureSuccessStatusCode();
var ret = // Convert response.Content into T.
return ret;
}
I would recommend throwing InvalidOperationExceptions for the StartFetch and CancelFetch operations if the IsFetching state is invalid. This may seem annoying but it lets you catch programmer error and threading issues before they get to be a bigger, hidden problem.
As for your asynchronous approach, your method should return a result. So maybe something like private async Task<MyHttpResult> StartTask(...). Your result should contain a way determine success, failure, and cancellation.
For example:
public sealed class MyHttpResult
{
public HttpResponse Result { get; private set; }
public Exception Error { get; private set; }
public bool WasCancelled { get; private set; }
public MyHttpResult(HttpResponse result, Exception error, bool wasCancelled)
{
this.Result = result;
this.Error = error;
this.WasCancelled = wasCancelled;
}
}
Many async methods will throw a TaskCanceledException if they are cancelled, so you can catch that to signify, like so:
async Task<MyHttpResult> StartTask(LFHttpRequest request, CancellationToken cancellationToken)
{
var message = new HttpRequestMessage(new HttpMethod(request.Method), request.Uri);
HttpResponse response = null;
Exception lastError = null;
bool wasCancelled = false;
try
{
response = await MessageInvoker.SendAsync(message, cancellationToken);
}
catch(TaskCanceledException)
{
wasCancelled = true;
}
catch(Exception ex)
{
lastError = ex;
}
var result = new MyHttpResult(response, lastError, wasCancelled);
return result;
}
This is all assuming that your observers are also the callers, so they can await this method. If this is not the case, your idea of an EventHandler makes sense. Instead of returning the result, you could create a custom EventArgs class like so:
public delegate void TaskResultEventHandler<T>(object sender, TaskResultEventArgs<T> e);
public sealed class TaskResultEventArgs<T> : EventArgs
{
public T Result { get; private set; }
public Exception Error { get; private set; }
public bool WasCancelled { get; private set; }
public TaskResultEventArgs(T result, Exception error, bool wasCancelled)
{
this.Result = result;
this.Error = error;
this.WasCancelled = wasCancelled;
}
}
Then it's simply a matter of exposing a TaskResultEventHandler<HttpResponse> and your observers subscribing to it. You could invoke it like so:
var handler = this.HttpTaskCompleted;
if(handler != null)
handler(this, new TaskResultEventArgs<HttpResponse>(response, lastError, wasCancelled));
After you awaited the http call
var response = await HttpClient.SendAsync(message, cancellationToken);
You should test for cancellation:
if(cancellationToken.IsCancellationRequested)
//... do what you want, throw or return false or null, depending on how you want to handle this cancellation.
Or you can check and throw the Microsoft exception in one call :
cancel.ThrowIfCancellationRequested();