-
-
Save jrgcubano/8602120c00acaf64ae1e to your computer and use it in GitHub Desktop.
C# classes for calling methods asynchronously and continue with with other methods after execution completes. Very useful in View Models.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Used to call methods asynchronously and continue with with other methods | |
/// after execution completes. Very useful in View Models. | |
/// </summary> | |
public class AsyncMethodCaller : IAsyncMethodCaller | |
{ | |
/// <summary> | |
/// The final <see cref="Task"/> to be executed, can be awaited. | |
/// </summary> | |
protected Task CurrentTask; | |
/// <summary> | |
/// Calls the given method asynchronously, and completes with the continueWith <see cref="T:System.Action`1{T}"/> | |
/// if the method call succeeds or the failWith <see cref="T:System.Action`1{T}"/> if the method throws an exception. | |
/// </summary> | |
/// <typeparam name="T">The return type of the method to be called.</typeparam> | |
/// <param name="methodToCall">The method to be called asynchronously.</param> | |
/// <param name="continueWith">The method to be continued with if the previously called method executed successfully.</param> | |
/// <param name="failWith">The method to be continued with if the previously called method threw an exception.</param> | |
public void CallMethodAndContinue<T>(Func<T> methodToCall, Action<T> continueWith, Action<Exception> failWith) | |
{ | |
var serviceTask = new Task<T>(methodToCall); | |
CurrentTask = serviceTask.ContinueWith(antecedent => | |
{ | |
if (antecedent.IsFaulted) | |
{ | |
failWith(antecedent.Exception); | |
} | |
else | |
{ | |
continueWith(serviceTask.Result); | |
} | |
}, TaskScheduler.FromCurrentSynchronizationContext()); | |
serviceTask.Start(); | |
} | |
/// <summary> | |
/// Calls the given method asynchronously, and completes with the continueWith <see cref="Action"/> | |
/// if the method call succeeds or the failWith <see cref="Action"/> if the method throws an exception. | |
/// </summary> | |
/// <param name="methodToCall">The method to be called asynchronously.</param> | |
/// <param name="continueWith">The method to be continued with if the previously called method executed successfully.</param> | |
/// <param name="failWith">The method to be continued with if the previously called method threw an exception.</param> | |
public void CallMethodAndContinue(Action methodToCall, Action continueWith, Action<Exception> failWith) | |
{ | |
var serviceTask = new Task(methodToCall); | |
CurrentTask = serviceTask.ContinueWith(antecedent => | |
{ | |
if (antecedent.IsFaulted) | |
{ | |
failWith(antecedent.Exception); | |
} | |
else | |
{ | |
continueWith(); | |
} | |
}, TaskScheduler.FromCurrentSynchronizationContext()); | |
serviceTask.Start(); | |
} | |
} | |
public interface IAsyncMethodCaller | |
{ | |
void CallMethodAndContinue<T>(Func<T> methodToCall, Action<T> continueWith, Action<Exception> failWith); | |
void CallMethodAndContinue(Action methodToCall, Action continueWith, Action<Exception> failWith); | |
} | |
/// <summary> | |
/// Helps with testing of behavior in View Models. | |
/// </summary> | |
public class TestAsyncMethodCaller : AsyncMethodCaller | |
{ | |
/// <summary> | |
/// Fake services can wait on this <see cref="ManualResetEventSlim"/>. | |
/// </summary> | |
public ManualResetEventSlim ManualResetEvent { get; private set; } | |
/// <summary> | |
/// Creates a <see cref="ManualResetEventSlim"/> and a <see cref="SynchronizationContext"/>. | |
/// </summary> | |
public TestAsyncMethodCaller() | |
{ | |
ManualResetEvent = new ManualResetEventSlim(false); | |
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); | |
} | |
/// <summary> | |
/// Signals a waiting service to continue and waits for View Model- | |
/// tasks to finish. | |
/// </summary> | |
public void StartServiceAndWait() | |
{ | |
ManualResetEvent.Set(); | |
CurrentTask.Wait(); | |
} | |
} | |
/**************************** TESTS *****************************/ | |
[TestClass] | |
public class AsyncMethodCallerTests | |
{ | |
private TestAsyncMethodCaller m_asyncMethodCaller; | |
private ServiceImplmentationForTest m_service; | |
private ViewModel m_viewModel; | |
private void Init(bool fail = false) | |
{ | |
m_asyncMethodCaller = new TestAsyncMethodCaller(); | |
m_service = new ServiceImplmentationForTest(m_asyncMethodCaller.ManualResetEvent, fail); | |
m_viewModel = new ViewModel(m_asyncMethodCaller, m_service); | |
} | |
[TestMethod] | |
public void CallMethodAndContinue_ServiceWithResultCompletes_ResultIsReturnedAndCallbackCalled() | |
{ | |
Init(); | |
m_viewModel.LoadContent(); | |
Assert.AreEqual("Loading content...", m_viewModel.MessageToUser); | |
m_asyncMethodCaller.StartServiceAndWait(); | |
Assert.AreEqual("Content is 1", m_viewModel.MessageToUser); | |
Assert.IsTrue(m_service.LoadContentCalled); | |
Assert.IsFalse(m_service.SaveCalled); | |
} | |
[TestMethod] | |
public void CallMethodAndContinue_ServiceWithResultFails_ExceptionIsReturnedAndCallbackCalled() | |
{ | |
Init(true); | |
m_viewModel.LoadContent(); | |
Assert.AreEqual("Loading content...", m_viewModel.MessageToUser); | |
m_asyncMethodCaller.StartServiceAndWait(); | |
Assert.AreEqual("Loading content failed: Exception Message", m_viewModel.MessageToUser); | |
Assert.IsTrue(m_service.LoadContentCalled); | |
Assert.IsFalse(m_service.SaveCalled); | |
} | |
[TestMethod] | |
public void CallMethodAndContinue_ServiceCompletes_CallbackCalled() | |
{ | |
Init(); | |
m_viewModel.Save(); | |
Assert.AreEqual("Saving content...", m_viewModel.MessageToUser); | |
m_asyncMethodCaller.StartServiceAndWait(); | |
Assert.AreEqual("Content saved", m_viewModel.MessageToUser); | |
Assert.IsTrue(m_service.SaveCalled); | |
Assert.IsFalse(m_service.LoadContentCalled); | |
} | |
[TestMethod] | |
public void CallMethodAndContinue_Service_ExceptionIsReturnedAndCallbackCalled() | |
{ | |
Init(true); | |
m_viewModel.Save(); | |
Assert.AreEqual("Saving content...", m_viewModel.MessageToUser); | |
m_asyncMethodCaller.StartServiceAndWait(); | |
Assert.AreEqual("Saving content failed: Exception Message", m_viewModel.MessageToUser); | |
Assert.IsTrue(m_service.SaveCalled); | |
Assert.IsFalse(m_service.LoadContentCalled); | |
} | |
private class ViewModel | |
{ | |
private readonly IAsyncMethodCaller m_asyncMethodCaller; | |
private readonly IServiceInterface m_service; | |
public string MessageToUser { get; private set; } | |
public ViewModel(IAsyncMethodCaller asyncMethodCaller, IServiceInterface service) | |
{ | |
m_asyncMethodCaller = asyncMethodCaller; | |
m_service = service; | |
} | |
public void LoadContent() | |
{ | |
MessageToUser = "Loading content..."; | |
m_asyncMethodCaller.CallMethodAndContinue(() => m_service.LoadContent(), | |
LoadContentCompleted, | |
LoadContentFailed); | |
} | |
public void Save() | |
{ | |
MessageToUser = "Saving content..."; | |
m_asyncMethodCaller.CallMethodAndContinue(() => m_service.Save(), | |
SaveCompleted, | |
SaveFailed); | |
} | |
private void LoadContentCompleted(int content) | |
{ | |
MessageToUser = "Content is " + content; | |
} | |
private void LoadContentFailed(Exception exception) | |
{ | |
MessageToUser = "Loading content failed: " + exception.InnerException.Message; | |
} | |
private void SaveCompleted() | |
{ | |
MessageToUser = "Content saved"; | |
} | |
private void SaveFailed(Exception exception) | |
{ | |
MessageToUser = "Saving content failed: " + exception.InnerException.Message; | |
} | |
} | |
private interface IServiceInterface | |
{ | |
int LoadContent(); | |
void Save(); | |
} | |
private class ServiceImplmentationForTest : IServiceInterface | |
{ | |
private readonly ManualResetEventSlim m_manualResetEvent; | |
private readonly bool m_fail; | |
public bool LoadContentCalled { get; private set; } | |
public bool SaveCalled { get; private set; } | |
public ServiceImplmentationForTest(ManualResetEventSlim manualResetEvent, bool fail) | |
{ | |
m_manualResetEvent = manualResetEvent; | |
m_fail = fail; | |
} | |
public int LoadContent() | |
{ | |
m_manualResetEvent.Wait(); | |
LoadContentCalled = true; | |
if (m_fail) | |
{ | |
throw new Exception("Exception Message"); | |
} | |
return 1; | |
} | |
public void Save() | |
{ | |
m_manualResetEvent.Wait(); | |
SaveCalled = true; | |
if (m_fail) | |
{ | |
throw new Exception("Exception Message"); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment