PageRenderTime 26ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs

https://bitbucket.org/mdavid/aspnetwebstack
C# | 263 lines | 207 code | 37 blank | 19 comment | 31 complexity | 559b7ece3f63033768a0318c78d677dd MD5 | raw file
  1. using System.Collections.Concurrent;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. using System.Reflection;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using System.Web.Mvc.Properties;
  10. namespace System.Web.Mvc.Async
  11. {
  12. /// <summary>
  13. /// When an action method returns either Task or Task{T} the TaskAsyncActionDescriptor provides information about the action.
  14. /// </summary>
  15. public class TaskAsyncActionDescriptor : AsyncActionDescriptor
  16. {
  17. /// <summary>
  18. /// dictionary to hold methods that can read Task{T}.Result
  19. /// </summary>
  20. private static readonly ConcurrentDictionary<Type, Func<object, object>> _taskValueExtractors = new ConcurrentDictionary<Type, Func<object, object>>();
  21. private readonly string _actionName;
  22. private readonly ControllerDescriptor _controllerDescriptor;
  23. private readonly Lazy<string> _uniqueId;
  24. private ParameterDescriptor[] _parametersCache;
  25. public TaskAsyncActionDescriptor(MethodInfo taskMethodInfo, string actionName, ControllerDescriptor controllerDescriptor)
  26. : this(taskMethodInfo, actionName, controllerDescriptor, validateMethod: true)
  27. {
  28. }
  29. internal TaskAsyncActionDescriptor(MethodInfo taskMethodInfo, string actionName, ControllerDescriptor controllerDescriptor, bool validateMethod)
  30. {
  31. if (taskMethodInfo == null)
  32. {
  33. throw new ArgumentNullException("taskMethodInfo");
  34. }
  35. if (String.IsNullOrEmpty(actionName))
  36. {
  37. throw Error.ParameterCannotBeNullOrEmpty("actionName");
  38. }
  39. if (controllerDescriptor == null)
  40. {
  41. throw new ArgumentNullException("controllerDescriptor");
  42. }
  43. if (validateMethod)
  44. {
  45. string taskFailedMessage = VerifyActionMethodIsCallable(taskMethodInfo);
  46. if (taskFailedMessage != null)
  47. {
  48. throw new ArgumentException(taskFailedMessage, "taskMethodInfo");
  49. }
  50. }
  51. TaskMethodInfo = taskMethodInfo;
  52. _actionName = actionName;
  53. _controllerDescriptor = controllerDescriptor;
  54. _uniqueId = new Lazy<string>(CreateUniqueId);
  55. }
  56. public override string ActionName
  57. {
  58. get { return _actionName; }
  59. }
  60. public MethodInfo TaskMethodInfo { get; private set; }
  61. public override ControllerDescriptor ControllerDescriptor
  62. {
  63. get { return _controllerDescriptor; }
  64. }
  65. public override string UniqueId
  66. {
  67. get { return _uniqueId.Value; }
  68. }
  69. private string CreateUniqueId()
  70. {
  71. return base.UniqueId + DescriptorUtil.CreateUniqueId(TaskMethodInfo);
  72. }
  73. public override IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state)
  74. {
  75. if (controllerContext == null)
  76. {
  77. throw new ArgumentNullException("controllerContext");
  78. }
  79. if (parameters == null)
  80. {
  81. throw new ArgumentNullException("parameters");
  82. }
  83. ParameterInfo[] parameterInfos = TaskMethodInfo.GetParameters();
  84. var rawParameterValues = from parameterInfo in parameterInfos
  85. select ExtractParameterFromDictionary(parameterInfo, parameters, TaskMethodInfo);
  86. object[] parametersArray = rawParameterValues.ToArray();
  87. CancellationTokenSource tokenSource = null;
  88. bool disposedTimer = false;
  89. Timer taskCancelledTimer = null;
  90. bool taskCancelledTimerRequired = false;
  91. int timeout = GetAsyncManager(controllerContext.Controller).Timeout;
  92. for (int i = 0; i < parametersArray.Length; i++)
  93. {
  94. if (default(CancellationToken).Equals(parametersArray[i]))
  95. {
  96. tokenSource = new CancellationTokenSource();
  97. parametersArray[i] = tokenSource.Token;
  98. // If there is a timeout we will create a timer to cancel the task when the
  99. // timeout expires.
  100. taskCancelledTimerRequired = timeout > Timeout.Infinite;
  101. break;
  102. }
  103. }
  104. ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(TaskMethodInfo);
  105. if (taskCancelledTimerRequired)
  106. {
  107. taskCancelledTimer = new Timer(_ =>
  108. {
  109. lock (tokenSource)
  110. {
  111. if (!disposedTimer)
  112. {
  113. tokenSource.Cancel();
  114. }
  115. }
  116. }, state: null, dueTime: timeout, period: Timeout.Infinite);
  117. }
  118. Task taskUser = dispatcher.Execute(controllerContext.Controller, parametersArray) as Task;
  119. Action cleanupAtEndExecute = () =>
  120. {
  121. // Cleanup code that's run in EndExecute, after we've waited on the task value.
  122. if (taskCancelledTimer != null)
  123. {
  124. // Timer callback may still fire after Dispose is called.
  125. taskCancelledTimer.Dispose();
  126. }
  127. if (tokenSource != null)
  128. {
  129. lock (tokenSource)
  130. {
  131. disposedTimer = true;
  132. tokenSource.Dispose();
  133. if (tokenSource.IsCancellationRequested)
  134. {
  135. // Give Timeout exceptions higher priority over other exceptions, mainly OperationCancelled exceptions
  136. // that were signaled with out timeout token.
  137. throw new TimeoutException();
  138. }
  139. }
  140. }
  141. };
  142. TaskWrapperAsyncResult result = new TaskWrapperAsyncResult(taskUser, state, cleanupAtEndExecute);
  143. // if user supplied a callback, invoke that when their task has finished running.
  144. if (callback != null)
  145. {
  146. taskUser.Finally(() =>
  147. {
  148. callback(result);
  149. });
  150. }
  151. return result;
  152. }
  153. public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
  154. {
  155. string errorMessage = String.Format(CultureInfo.CurrentCulture, MvcResources.TaskAsyncActionDescriptor_CannotExecuteSynchronously,
  156. ActionName);
  157. throw new InvalidOperationException(errorMessage);
  158. }
  159. public override object EndExecute(IAsyncResult asyncResult)
  160. {
  161. TaskWrapperAsyncResult wrapperResult = (TaskWrapperAsyncResult)asyncResult;
  162. // Throw an exception with the correct call stack
  163. try
  164. {
  165. wrapperResult.Task.ThrowIfFaulted();
  166. }
  167. finally
  168. {
  169. if (wrapperResult.CleanupThunk != null)
  170. {
  171. wrapperResult.CleanupThunk();
  172. }
  173. }
  174. // Extract the result of the task if there is a result
  175. return _taskValueExtractors.GetOrAdd(TaskMethodInfo.ReturnType, CreateTaskValueExtractor)(wrapperResult.Task);
  176. }
  177. private static Func<object, object> CreateTaskValueExtractor(Type taskType)
  178. {
  179. // Task<T>?
  180. if (taskType.IsGenericType && taskType.GetGenericTypeDefinition() == typeof(Task<>))
  181. {
  182. // lambda = arg => (object)(((Task<T>)arg).Result)
  183. var arg = Expression.Parameter(typeof(object));
  184. var castArg = Expression.Convert(arg, taskType);
  185. var fieldAccess = Expression.Property(castArg, "Result");
  186. var castResult = Expression.Convert(fieldAccess, typeof(object));
  187. var lambda = Expression.Lambda<Func<object, object>>(castResult, arg);
  188. return lambda.Compile();
  189. }
  190. // Any exceptions should be thrown before getting the task value so just return null.
  191. return theTask =>
  192. {
  193. return null;
  194. };
  195. }
  196. public override object[] GetCustomAttributes(bool inherit)
  197. {
  198. return ActionDescriptorHelper.GetCustomAttributes(TaskMethodInfo, inherit);
  199. }
  200. public override object[] GetCustomAttributes(Type attributeType, bool inherit)
  201. {
  202. return ActionDescriptorHelper.GetCustomAttributes(TaskMethodInfo, attributeType, inherit);
  203. }
  204. public override ParameterDescriptor[] GetParameters()
  205. {
  206. return ActionDescriptorHelper.GetParameters(this, TaskMethodInfo, ref _parametersCache);
  207. }
  208. public override ICollection<ActionSelector> GetSelectors()
  209. {
  210. return ActionDescriptorHelper.GetSelectors(TaskMethodInfo);
  211. }
  212. public override bool IsDefined(Type attributeType, bool inherit)
  213. {
  214. return ActionDescriptorHelper.IsDefined(TaskMethodInfo, attributeType, inherit);
  215. }
  216. public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
  217. {
  218. if (useCache && GetType() == typeof(TaskAsyncActionDescriptor))
  219. {
  220. // Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
  221. return ReflectedAttributeCache.GetMethodFilterAttributes(TaskMethodInfo);
  222. }
  223. return base.GetFilterAttributes(useCache);
  224. }
  225. }
  226. }