Skip to content

Commit 5ef73d8

Browse files
authored
Merge pull request #40 from wintoncode/allow-opt-out-transaction-scope-wrapping
feat(Winton.Extensions.Threading.Actor): allow opt-out of transaction scope wrapping in enqueue work
2 parents 4ebc258 + 170c386 commit 5ef73d8

File tree

5 files changed

+50
-30
lines changed

5 files changed

+50
-30
lines changed

Winton.Extensions.Threading.Actor.Tests.Unit/Internal/ActorTaskSchedulerTests.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ public ActorTaskSchedulerTests()
3737
internal async Task ShouldBeAbleToResumeInitiallyPausedScheduler(ActorTaskTraits resumingTaskTraits)
3838
{
3939
var count = 0;
40-
var task1 = _actorTaskFactory.StartNew(() => ++count, CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.None);
41-
var task2 = _actorTaskFactory.StartNew(() => ++count, CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.None);
40+
var task1 = _actorTaskFactory.StartNew(() => ++count, CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.None);
41+
var task2 = _actorTaskFactory.StartNew(() => ++count, CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.None);
4242

4343
Task.WhenAny(task1, task2).Wait(TimeSpan.FromSeconds(1)).Should().BeFalse("tasks should not have been executed if the scheduler is paused");
4444

45-
var resumer = _actorTaskFactory.StartNew(() => ++count, CancellationToken.None, TaskCreationOptions.None, resumingTaskTraits);
45+
var resumer = _actorTaskFactory.StartNew(() => ++count, CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, resumingTaskTraits);
4646

4747
(await resumer).Should().Be(1);
4848
(await task1).Should().Be(2);
@@ -54,22 +54,22 @@ public void FailureOfCriticalTaskShouldTerminateSchedulerWithSubsequentTasksBein
5454
{
5555
UnpauseScheduler();
5656

57-
var task1 = _actorTaskFactory.StartNew(new Action(() => throw new Exception("oh dear")), CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.None);
57+
var task1 = _actorTaskFactory.StartNew(new Action(() => throw new Exception("oh dear")), CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.None);
5858

5959
task1.Awaiting(x => x).Should().Throw<Exception>().WithMessage("oh dear");
6060

6161
_scheduler.TerminatedTask.Wait(TimeSpan.FromMilliseconds(250)).Should().BeFalse();
6262

63-
var task2 = _actorTaskFactory.StartNew(new Action(() => throw new Exception("no!!!")), CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.Critical);
64-
var task3 = _actorTaskFactory.StartNew(new Action(() => throw new Exception("shouldn't hit this")), CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.None);
63+
var task2 = _actorTaskFactory.StartNew(new Action(() => throw new Exception("no!!!")), CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.Critical);
64+
var task3 = _actorTaskFactory.StartNew(new Action(() => throw new Exception("shouldn't hit this")), CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.None);
6565

6666
task2.Awaiting(x => x).Should().Throw<Exception>().WithMessage("no!!!");
6767

6868
ThrowIfWaitTimesOut(_scheduler.TerminatedTask);
6969

7070
task3.Awaiting(x => x).Should().Throw<TaskCanceledException>();
7171

72-
var task4 = _actorTaskFactory.StartNew(() => throw new Exception("shouldn't hit this either"), CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.None);
72+
var task4 = _actorTaskFactory.StartNew(() => throw new Exception("shouldn't hit this either"), CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.None);
7373

7474
task4.Awaiting(x => x).Should().Throw<TaskCanceledException>();
7575
}
@@ -95,8 +95,8 @@ void TerminalWork()
9595
}
9696
}
9797

98-
var task1 = _actorTaskFactory.StartNew(TerminalWork, CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.Terminal);
99-
var task2 = _actorTaskFactory.StartNew(() => throw new Exception("shouldn't hit this"), CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.None);
98+
var task1 = _actorTaskFactory.StartNew(TerminalWork, CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.Terminal);
99+
var task2 = _actorTaskFactory.StartNew(() => throw new Exception("shouldn't hit this"), CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.None);
100100

101101
switch (terminalWorkOutcomeType)
102102
{
@@ -116,7 +116,7 @@ void TerminalWork()
116116

117117
task2.Awaiting(x => x).Should().Throw<TaskCanceledException>();
118118

119-
var task3 = _actorTaskFactory.StartNew(() => throw new Exception("shouldn't hit this either"), CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.None);
119+
var task3 = _actorTaskFactory.StartNew(() => throw new Exception("shouldn't hit this either"), CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.None);
120120

121121
task3.Awaiting(x => x).Should().Throw<TaskCanceledException>();
122122
}
@@ -135,13 +135,13 @@ public void ForLongRunningTasksShouldUseActiveThreadIfNotFromThreadPool()
135135
ActorThreadAssertions.CurrentThreadShouldNotBeThreadPoolThread();
136136
ThrowIfWaitTimesOut(barrier.Task);
137137
task1ThreadId = Thread.CurrentThread.ManagedThreadId;
138-
}, CancellationToken.None, TaskCreationOptions.LongRunning, ActorTaskTraits.None);
138+
}, CancellationToken.None, TaskCreationOptions.LongRunning, ActorEnqueueOptions.Default, ActorTaskTraits.None);
139139
var task2 = _actorTaskFactory.StartNew(
140140
() =>
141141
{
142142
ActorThreadAssertions.CurrentThreadShouldNotBeThreadPoolThread();
143143
task2ThreadId = Thread.CurrentThread.ManagedThreadId;
144-
}, CancellationToken.None, TaskCreationOptions.LongRunning, ActorTaskTraits.None);
144+
}, CancellationToken.None, TaskCreationOptions.LongRunning, ActorEnqueueOptions.Default, ActorTaskTraits.None);
145145

146146
barrier.SetResult(true);
147147
ThrowIfWaitTimesOut(task1);
@@ -165,13 +165,13 @@ public void ForLongRunningTasksShouldUseNonThreadPoolThreadIfActiveThreadFromThr
165165
ActorThreadAssertions.CurrentThreadShouldBeThreadPoolThread();
166166
ThrowIfWaitTimesOut(barrier.Task);
167167
shortTaskThreadId = Thread.CurrentThread.ManagedThreadId;
168-
}, CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.None);
168+
}, CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.None);
169169
var longTask = _actorTaskFactory.StartNew(
170170
() =>
171171
{
172172
ActorThreadAssertions.CurrentThreadShouldNotBeThreadPoolThread();
173173
longTaskThreadId = Thread.CurrentThread.ManagedThreadId;
174-
}, CancellationToken.None, TaskCreationOptions.LongRunning, ActorTaskTraits.None);
174+
}, CancellationToken.None, TaskCreationOptions.LongRunning, ActorEnqueueOptions.Default, ActorTaskTraits.None);
175175

176176
barrier.SetResult(true);
177177

@@ -308,7 +308,7 @@ public void ShouldBeAbleToTerminateSchedulerSuchThatNoFurtherTasksAreExecuted(Ta
308308
_actorTaskFactory.StartNew(() =>
309309
{
310310
ThrowIfWaitTimesOut(barrier.Task);
311-
}, CancellationToken.None, terminalTaskCreationOptions, ActorTaskTraits.Terminal);
311+
}, CancellationToken.None, terminalTaskCreationOptions, ActorEnqueueOptions.Default, ActorTaskTraits.Terminal);
312312

313313
Task lateTask = default;
314314

@@ -455,7 +455,7 @@ async Task Action(Task<Task> x)
455455

456456
private void UnpauseScheduler()
457457
{
458-
_actorTaskFactory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, ActorTaskTraits.Resuming).Wait();
458+
_actorTaskFactory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, ActorTaskTraits.Resuming).Wait();
459459
}
460460
}
461461
}

Winton.Extensions.Threading.Actor/Actor.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,20 +205,20 @@ public Task Stop()
205205
/// <inheritdoc />
206206
public Task<T> Enqueue<T>(Func<Task<T>> asyncFunction, CancellationToken cancellationToken, ActorEnqueueOptions options) => Enqueue(asyncFunction, cancellationToken, options, ActorTaskTraits.None);
207207

208-
private Task Enqueue(Action action, CancellationToken cancellationToken, ActorEnqueueOptions options, ActorTaskTraits taskTraits) => _actorTaskFactory.StartNew(action, cancellationToken, ActorTaskOptions.GetTaskCreationOptions(options), taskTraits);
208+
private Task Enqueue(Action action, CancellationToken cancellationToken, ActorEnqueueOptions options, ActorTaskTraits taskTraits) => _actorTaskFactory.StartNew(action, cancellationToken, ActorTaskOptions.GetTaskCreationOptions(options), options, taskTraits);
209209

210-
private Task<T> Enqueue<T>(Func<T> function, CancellationToken cancellationToken, ActorEnqueueOptions options, ActorTaskTraits taskTraits) => _actorTaskFactory.StartNew(function, cancellationToken, ActorTaskOptions.GetTaskCreationOptions(options), taskTraits);
210+
private Task<T> Enqueue<T>(Func<T> function, CancellationToken cancellationToken, ActorEnqueueOptions options, ActorTaskTraits taskTraits) => _actorTaskFactory.StartNew(function, cancellationToken, ActorTaskOptions.GetTaskCreationOptions(options), options, taskTraits);
211211

212212
private async Task Enqueue(Func<Task> asyncAction, CancellationToken cancellationToken, ActorEnqueueOptions options, ActorTaskTraits taskTraits)
213213
{
214-
var task = _actorTaskFactory.StartNew(asyncAction, cancellationToken, ActorTaskOptions.GetTaskCreationOptions(options), taskTraits);
214+
var task = _actorTaskFactory.StartNew(asyncAction, cancellationToken, ActorTaskOptions.GetTaskCreationOptions(options), options, taskTraits);
215215
var nestedTask = await task.ConfigureAwait(false);
216216
await nestedTask.ConfigureAwait(false);
217217
}
218218

219219
private async Task<T> Enqueue<T>(Func<Task<T>> asyncFunction, CancellationToken cancellationToken, ActorEnqueueOptions options, ActorTaskTraits taskTraits)
220220
{
221-
var task = _actorTaskFactory.StartNew(asyncFunction, cancellationToken, ActorTaskOptions.GetTaskCreationOptions(options), taskTraits);
221+
var task = _actorTaskFactory.StartNew(asyncFunction, cancellationToken, ActorTaskOptions.GetTaskCreationOptions(options), options, taskTraits);
222222
var nestedTask = await task.ConfigureAwait(false);
223223
return await nestedTask.ConfigureAwait(false);
224224
}

Winton.Extensions.Threading.Actor/ActorEnqueueOptions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ public enum ActorEnqueueOptions
1818
/// <summary>
1919
/// Specifies that the work to be done will tend to be long-running.
2020
/// </summary>
21-
WorkIsLongRunning = 1
21+
WorkIsLongRunning = 1,
22+
/// <summary>
23+
/// Specifies that the work will not be wrapped with a block to suppress ambient transaction scope.
24+
/// </summary>
25+
NoSuppressTransactionScope = 2,
2226
}
2327
}

Winton.Extensions.Threading.Actor/Internal/ActorSynchronizationContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ public ActorSynchronizationContext(ActorTaskFactory actorTaskFactory, ActorTaskT
1616

1717
public ActorSynchronizationContext ChangeActorTaskKind(ActorTaskTraits actorTaskTraits) => new ActorSynchronizationContext(_actorTaskFactory, actorTaskTraits);
1818

19-
public override void Post(SendOrPostCallback callback, object state) => _actorTaskFactory.StartNew(() => callback(state), CancellationToken.None, TaskCreationOptions.None, _actorTaskTraits);
19+
public override void Post(SendOrPostCallback callback, object state) => _actorTaskFactory.StartNew(() => callback(state), CancellationToken.None, TaskCreationOptions.None, ActorEnqueueOptions.Default, _actorTaskTraits);
2020
}
2121
}

Winton.Extensions.Threading.Actor/Internal/ActorTaskFactory.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,60 @@ internal sealed class ActorTaskFactory
1010

1111
public ActorTaskFactory(ActorTaskScheduler taskScheduler) => _scheduler = taskScheduler;
1212

13-
public Task StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, ActorTaskTraits traits = ActorTaskTraits.None)
13+
public Task StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, ActorEnqueueOptions actorEnqueueOptions = ActorEnqueueOptions.Default, ActorTaskTraits traits = ActorTaskTraits.None)
1414
{
1515
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
1616

1717
ActorTask.CurrentActorTaskTraits = traits;
1818
ActorTask.CurrentCanceller = cancellationTokenSource;
1919

20-
return Task.Factory.StartNew(ActorExtensions.SuppressTransactionScopeWrapper(action), cancellationTokenSource.Token, taskCreationOptions | TaskCreationOptions.HideScheduler, _scheduler);
20+
return Task.Factory.StartNew(
21+
actorEnqueueOptions.HasFlag(ActorEnqueueOptions.NoSuppressTransactionScope) ? action : ActorExtensions.SuppressTransactionScopeWrapper(action),
22+
cancellationTokenSource.Token,
23+
taskCreationOptions | TaskCreationOptions.HideScheduler,
24+
_scheduler);
2125
}
2226

23-
public Task<Task> StartNew(Func<Task> asyncFunction, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, ActorTaskTraits traits = ActorTaskTraits.None)
27+
public Task<Task> StartNew(Func<Task> asyncFunction, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, ActorEnqueueOptions actorEnqueueOptions = ActorEnqueueOptions.Default, ActorTaskTraits traits = ActorTaskTraits.None)
2428
{
2529
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
2630

2731
ActorTask.CurrentActorTaskTraits = traits;
2832
ActorTask.CurrentCanceller = cancellationTokenSource;
2933

30-
return Task.Factory.StartNew(ActorExtensions.SuppressTransactionScopeWrapper(asyncFunction), cancellationTokenSource.Token, taskCreationOptions | TaskCreationOptions.HideScheduler, _scheduler);
34+
return Task.Factory.StartNew(
35+
actorEnqueueOptions.HasFlag(ActorEnqueueOptions.NoSuppressTransactionScope) ? asyncFunction : ActorExtensions.SuppressTransactionScopeWrapper(asyncFunction),
36+
cancellationTokenSource.Token,
37+
taskCreationOptions | TaskCreationOptions.HideScheduler,
38+
_scheduler);
3139
}
3240

33-
public Task<Task<T>> StartNew<T>(Func<Task<T>> asyncFunction, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, ActorTaskTraits traits = ActorTaskTraits.None)
41+
public Task<Task<T>> StartNew<T>(Func<Task<T>> asyncFunction, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, ActorEnqueueOptions actorEnqueueOptions = ActorEnqueueOptions.Default, ActorTaskTraits traits = ActorTaskTraits.None)
3442
{
3543
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
3644

3745
ActorTask.CurrentActorTaskTraits = traits;
3846
ActorTask.CurrentCanceller = cancellationTokenSource;
3947

40-
return Task.Factory.StartNew(ActorExtensions.SuppressTransactionScopeWrapper(asyncFunction), cancellationTokenSource.Token, taskCreationOptions | TaskCreationOptions.HideScheduler, _scheduler);
48+
return Task.Factory.StartNew(
49+
actorEnqueueOptions.HasFlag(ActorEnqueueOptions.NoSuppressTransactionScope) ? asyncFunction : ActorExtensions.SuppressTransactionScopeWrapper(asyncFunction),
50+
cancellationTokenSource.Token,
51+
taskCreationOptions | TaskCreationOptions.HideScheduler,
52+
_scheduler);
4153
}
4254

43-
public Task<T> StartNew<T>(Func<T> function, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, ActorTaskTraits traits = ActorTaskTraits.None)
55+
public Task<T> StartNew<T>(Func<T> function, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, ActorEnqueueOptions actorEnqueueOptions = ActorEnqueueOptions.Default, ActorTaskTraits traits = ActorTaskTraits.None)
4456
{
4557
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
4658

4759
ActorTask.CurrentActorTaskTraits = traits;
4860
ActorTask.CurrentCanceller = cancellationTokenSource;
4961

50-
return Task.Factory.StartNew(ActorExtensions.SuppressTransactionScopeWrapper(function), cancellationTokenSource.Token, taskCreationOptions | TaskCreationOptions.HideScheduler, _scheduler);
62+
return Task.Factory.StartNew(
63+
actorEnqueueOptions.HasFlag(ActorEnqueueOptions.NoSuppressTransactionScope) ? function : ActorExtensions.SuppressTransactionScopeWrapper(function),
64+
cancellationTokenSource.Token,
65+
taskCreationOptions | TaskCreationOptions.HideScheduler,
66+
_scheduler);
5167
}
5268
}
5369
}

0 commit comments

Comments
 (0)