Skip to content

Commit ef8ab58

Browse files
committed
Heavily refactored async behoviour throughout the app. Now every background task (TPL or ITask) gets wrapped in AsyncRunner.Run(), which ensures that a single thread-specific lifetime scope is spawned for the unit of work. Increases stability and performance in background tasks condiderably.
1 parent 84c2566 commit ef8ab58

27 files changed

Lines changed: 707 additions & 364 deletions

src/Libraries/SmartStore.Core/Async/AsyncRunner.cs

Lines changed: 178 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22
using System.Collections.Generic;
33
using System.Threading;
44
using System.Threading.Tasks;
5+
using System.Web.Hosting;
56
using Autofac;
67
using SmartStore.Core.Infrastructure;
7-
using SmartStore.Core.Infrastructure.DependencyManagement;
8+
using SmartStore.Utilities;
89

910
namespace SmartStore.Core.Async
1011
{
1112

1213
public static class AsyncRunner
1314
{
15+
private static readonly BackgroundWorkHost _host = new BackgroundWorkHost();
16+
1417

1518
/// <summary>
1619
/// Execute's an async Task<T> method which has a void return value synchronously
@@ -75,59 +78,143 @@ public static TResult RunSync<TResult>(Func<Task<TResult>> func)
7578
return ret;
7679
}
7780

78-
public static Task Run(Action<ILifetimeScope> action)
81+
public static Task Run(Action<ILifetimeScope, CancellationToken> action)
7982
{
8083
return Run(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
8184
}
8285

83-
public static Task Run(Action<ILifetimeScope> action, CancellationToken cancellationToken)
86+
public static Task Run(Action<ILifetimeScope, CancellationToken> action, CancellationToken cancellationToken)
8487
{
8588
return Run(action, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
8689
}
8790

88-
public static Task Run(Action<ILifetimeScope> action, TaskCreationOptions options)
91+
public static Task Run(Action<ILifetimeScope, CancellationToken> action, TaskCreationOptions options)
8992
{
9093
return Run(action, CancellationToken.None, options, TaskScheduler.Default);
9194
}
9295

93-
public static Task Run(Action<ILifetimeScope> action, CancellationToken cancellationToken, TaskCreationOptions options)
96+
public static Task Run(Action<ILifetimeScope, CancellationToken> action, CancellationToken cancellationToken, TaskCreationOptions options)
9497
{
9598
return Run(action, cancellationToken, options, TaskScheduler.Default);
9699
}
97100

98-
public static Task Run(Action<ILifetimeScope> action, TaskScheduler scheduler)
101+
public static Task Run(Action<ILifetimeScope, CancellationToken> action, TaskScheduler scheduler)
99102
{
100103
return Run(action, CancellationToken.None, TaskCreationOptions.None, scheduler);
101104
}
102-
103-
public static Task Run(Action<ILifetimeScope> action, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
105+
106+
public static Task Run(Action<ILifetimeScope, CancellationToken> action, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
104107
{
105108
Guard.ArgumentNotNull(() => action);
106109
Guard.ArgumentNotNull(() => scheduler);
107110

111+
var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
112+
options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
113+
108114
var t = Task.Factory.StartNew(() => {
109-
using (var container = EngineContext.Current.ContainerManager.Container.BeginLifetimeScope(AutofacLifetimeScopeProvider.HttpRequestTag))
115+
var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
116+
using (var scope = accessor.BeginContextAwareScope())
110117
{
111-
action(container);
118+
action(accessor.GetLifetimeScope(null), ct);
112119
}
113-
}, cancellationToken, options, scheduler);
120+
}, ct, options, scheduler);
121+
122+
_host.Register(t, ct);
114123

115124
return t;
116125
}
117126

118-
public static Task Run(Action<ILifetimeScope, object> action, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
127+
public static Task Run(Action<ILifetimeScope, CancellationToken, object> action, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
119128
{
120129
Guard.ArgumentNotNull(() => state);
121130
Guard.ArgumentNotNull(() => action);
122131
Guard.ArgumentNotNull(() => scheduler);
123132

133+
var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
134+
options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
135+
136+
var t = Task.Factory.StartNew((o) =>
137+
{
138+
var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
139+
using (var scope = accessor.BeginContextAwareScope())
140+
{
141+
action(accessor.GetLifetimeScope(null), ct, o);
142+
}
143+
}, state, ct, options, scheduler);
144+
145+
_host.Register(t, ct);
146+
147+
return t;
148+
}
149+
150+
151+
152+
public static Task<TResult> Run<TResult>(Func<ILifetimeScope, CancellationToken, TResult> function)
153+
{
154+
return Run(function, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
155+
}
156+
157+
public static Task<TResult> Run<TResult>(Func<ILifetimeScope, CancellationToken, TResult> function, CancellationToken cancellationToken)
158+
{
159+
return Run(function, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
160+
}
161+
162+
public static Task<TResult> Run<TResult>(Func<ILifetimeScope, CancellationToken, TResult> function, TaskCreationOptions options)
163+
{
164+
return Run(function, CancellationToken.None, options, TaskScheduler.Default);
165+
}
166+
167+
public static Task<TResult> Run<TResult>(Func<ILifetimeScope, CancellationToken, TResult> function, CancellationToken cancellationToken, TaskCreationOptions options)
168+
{
169+
return Run(function, cancellationToken, options, TaskScheduler.Default);
170+
}
171+
172+
public static Task<TResult> Run<TResult>(Func<ILifetimeScope, CancellationToken, TResult> function, TaskScheduler scheduler)
173+
{
174+
return Run(function, CancellationToken.None, TaskCreationOptions.None, scheduler);
175+
}
176+
177+
public static Task<TResult> Run<TResult>(Func<ILifetimeScope, CancellationToken, TResult> function, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
178+
{
179+
Guard.ArgumentNotNull(() => function);
180+
Guard.ArgumentNotNull(() => scheduler);
181+
182+
var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
183+
options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
184+
185+
var t = Task.Factory.StartNew(() =>
186+
{
187+
var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
188+
using (var scope = accessor.BeginContextAwareScope())
189+
{
190+
return function(accessor.GetLifetimeScope(null), ct);
191+
}
192+
}, ct, options, scheduler);
193+
194+
_host.Register(t, ct);
195+
196+
return t;
197+
}
198+
199+
public static Task<TResult> Run<TResult>(Func<ILifetimeScope, CancellationToken, object, TResult> function, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler)
200+
{
201+
Guard.ArgumentNotNull(() => state);
202+
Guard.ArgumentNotNull(() => function);
203+
Guard.ArgumentNotNull(() => scheduler);
204+
205+
var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token;
206+
options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool)
207+
124208
var t = Task.Factory.StartNew((o) =>
125209
{
126-
using (var container = EngineContext.Current.ContainerManager.Container.BeginLifetimeScope(AutofacLifetimeScopeProvider.HttpRequestTag))
210+
var accessor = EngineContext.Current.ContainerManager.ScopeAccessor;
211+
using (var scope = accessor.BeginContextAwareScope())
127212
{
128-
action(container, o);
213+
return function(accessor.GetLifetimeScope(null), ct, o);
129214
}
130-
}, state, cancellationToken, options, scheduler);
215+
}, state, ct, options, scheduler);
216+
217+
_host.Register(t, ct);
131218

132219
return t;
133220
}
@@ -193,4 +280,80 @@ public override SynchronizationContext CreateCopy()
193280

194281
}
195282

283+
internal class BackgroundWorkHost : IRegisteredObject
284+
{
285+
private CancellationTokenSource _shutdownCancellationTokenSource = new CancellationTokenSource();
286+
private int _numRunningWorkItems = 0;
287+
288+
public BackgroundWorkHost()
289+
{
290+
HostingEnvironment.RegisterObject(this);
291+
}
292+
293+
public void Stop(bool immediate)
294+
{
295+
int num;
296+
lock (this)
297+
{
298+
_shutdownCancellationTokenSource.Cancel();
299+
num = _numRunningWorkItems;
300+
}
301+
if (num == 0)
302+
{
303+
FinalShutdown();
304+
}
305+
}
306+
307+
public CancellationTokenSource CreateCompositeCancellationTokenSource(CancellationToken userCancellationToken)
308+
{
309+
if (userCancellationToken == CancellationToken.None)
310+
{
311+
return _shutdownCancellationTokenSource;
312+
}
313+
return CancellationTokenSource.CreateLinkedTokenSource(_shutdownCancellationTokenSource.Token, userCancellationToken);
314+
}
315+
316+
public void Register(Task work, CancellationToken cancellationToken)
317+
{
318+
if (!cancellationToken.IsCancellationRequested)
319+
{
320+
lock (this)
321+
{
322+
if (cancellationToken.IsCancellationRequested)
323+
{
324+
return;
325+
}
326+
_numRunningWorkItems++;
327+
}
328+
329+
work.ContinueWith(
330+
WorkItemComplete,
331+
CancellationToken.None,
332+
TaskContinuationOptions.ExecuteSynchronously,
333+
TaskScheduler.Default);
334+
}
335+
}
336+
337+
private void WorkItemComplete(Task work)
338+
{
339+
int num;
340+
bool isCancellationRequested;
341+
lock (this)
342+
{
343+
num = --_numRunningWorkItems;
344+
isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested;
345+
}
346+
if (num == 0 && isCancellationRequested)
347+
{
348+
FinalShutdown();
349+
}
350+
}
351+
352+
private void FinalShutdown()
353+
{
354+
HostingEnvironment.UnregisterObject(this);
355+
}
356+
357+
}
358+
196359
}

src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,6 @@ public static TAttribute GetAttribute<TAttribute>(this ICustomAttributeProvider
544544
}
545545

546546
return null;
547-
548547
}
549548

550549
public static bool HasAttribute<TAttribute>(this ICustomAttributeProvider target, bool inherits) where TAttribute : Attribute
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Runtime.Remoting.Messaging;
3+
using System.Threading;
4+
using System.Web;
5+
6+
namespace SmartStore.Core.Infrastructure
7+
{
8+
/// <summary>
9+
/// Holds some state for the current HttpContext or thread
10+
/// </summary>
11+
/// <typeparam name="T">The type of data to store</typeparam>
12+
public class ContextState<T> where T : class
13+
{
14+
private readonly string _name;
15+
private readonly Func<T> _defaultValue;
16+
17+
public ContextState(string name)
18+
{
19+
_name = name;
20+
}
21+
22+
public ContextState(string name, Func<T> defaultValue)
23+
{
24+
_name = name;
25+
_defaultValue = defaultValue;
26+
}
27+
28+
public T GetState()
29+
{
30+
if (HttpContext.Current == null)
31+
{
32+
var data = CallContext.GetData(_name);
33+
34+
if (data == null)
35+
{
36+
if (_defaultValue != null)
37+
{
38+
CallContext.SetData(_name, data = _defaultValue());
39+
return data as T;
40+
}
41+
}
42+
43+
return data as T;
44+
}
45+
46+
if (HttpContext.Current.Items[_name] == null)
47+
{
48+
HttpContext.Current.Items[_name] = _defaultValue == null ? null : _defaultValue();
49+
}
50+
51+
return HttpContext.Current.Items[_name] as T;
52+
}
53+
54+
public void SetState(T state)
55+
{
56+
if (HttpContext.Current == null)
57+
{
58+
CallContext.SetData(_name, state);
59+
}
60+
else
61+
{
62+
HttpContext.Current.Items[_name] = state;
63+
}
64+
}
65+
66+
public void RemoveState()
67+
{
68+
if (HttpContext.Current == null)
69+
{
70+
CallContext.FreeNamedDataSlot(_name);
71+
}
72+
else
73+
{
74+
if (HttpContext.Current.Items.Contains(_name))
75+
{
76+
HttpContext.Current.Items.Remove(_name);
77+
}
78+
}
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)