Skip to content

Commit

Permalink
Merge branch 'master' of github.com:OmerMor/AsyncBridge
Browse files Browse the repository at this point in the history
  • Loading branch information
OmerMor committed Apr 13, 2012
2 parents e9fa1aa + 627d325 commit efaf223
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/AsyncBridge/AsyncBridge.csproj
Expand Up @@ -36,6 +36,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="AsyncTaskMethodBuilder.cs" /> <Compile Include="AsyncTaskMethodBuilder.cs" />
<Compile Include="AsyncVoidMethodBuilder.cs" /> <Compile Include="AsyncVoidMethodBuilder.cs" />
<Compile Include="ConfigurableTaskAwaitable.cs" />
<Compile Include="DelayTask.cs" /> <Compile Include="DelayTask.cs" />
<Compile Include="TaskUtils.cs" /> <Compile Include="TaskUtils.cs" />
<Compile Include="VoidTaskResult.cs" /> <Compile Include="VoidTaskResult.cs" />
Expand Down
42 changes: 42 additions & 0 deletions src/AsyncBridge/ConfigurableTaskAwaitable.cs
@@ -0,0 +1,42 @@
using System.Threading.Tasks;

namespace AsyncBridge
{
/// <summary>
/// An awaitable which wraps a class, maybe preventing it from capturing the SynchronizationContext
/// </summary>
public class ConfigurableTaskAwaitable<T>
{
private readonly Task<T> m_task;
private readonly bool m_useCapturedContext;

public ConfigurableTaskAwaitable(Task<T> task, bool useCapturedContext)
{
m_task = task;
m_useCapturedContext = useCapturedContext;
}

public TaskAwaiter<T> GetAwaiter()
{
return new TaskAwaiter<T>(m_task, m_useCapturedContext);
}
}

// ZOMG why isn't void an actual type
public class ConfigurableTaskAwaitable
{
private readonly Task m_task;
private readonly bool m_useCapturedContext;

public ConfigurableTaskAwaitable(Task task, bool useCapturedContext)
{
m_task = task;
m_useCapturedContext = useCapturedContext;
}

public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter(m_task, m_useCapturedContext);
}
}
}
18 changes: 13 additions & 5 deletions src/AsyncBridge/TaskAwaiter.cs
Expand Up @@ -5,18 +5,20 @@ namespace System.Threading.Tasks
public struct TaskAwaiter : INotifyCompletion public struct TaskAwaiter : INotifyCompletion
{ {
private readonly Task m_task; private readonly Task m_task;
private readonly bool m_useCapturedContext;


internal TaskAwaiter(Task task) internal TaskAwaiter(Task task, bool useCapturedContext = true)
{ {
m_task = task; m_task = task;
m_useCapturedContext = useCapturedContext;
} }


internal static TaskScheduler TaskScheduler internal static TaskScheduler TaskScheduler
{ {
get get
{ {
var taskScheduler = SynchronizationContext.Current == null var taskScheduler = SynchronizationContext.Current == null
? TaskScheduler.Default ? TaskScheduler.Current
: TaskScheduler.FromCurrentSynchronizationContext(); : TaskScheduler.FromCurrentSynchronizationContext();
return taskScheduler; return taskScheduler;
} }
Expand All @@ -30,7 +32,9 @@ public bool IsCompleted
public void OnCompleted(Action continuation) public void OnCompleted(Action continuation)
{ {
m_task.ContinueWith( m_task.ContinueWith(
delegate { continuation(); }, TaskScheduler); delegate { continuation(); },
// I don't think continuing on the thread pool is what people really wanted when they called ConfigureAwait, but it's what the CTP did
m_useCapturedContext ? TaskScheduler : TaskScheduler.Default);
} }


public void GetResult() public void GetResult()
Expand All @@ -49,10 +53,12 @@ public void GetResult()
public struct TaskAwaiter<T> : INotifyCompletion public struct TaskAwaiter<T> : INotifyCompletion
{ {
private readonly Task<T> m_task; private readonly Task<T> m_task;
private readonly bool m_useCapturedContext;


internal TaskAwaiter(Task<T> task) public TaskAwaiter(Task<T> task, bool useCapturedContext = true)
{ {
m_task = task; m_task = task;
m_useCapturedContext = useCapturedContext;
} }


public bool IsCompleted public bool IsCompleted
Expand All @@ -63,7 +69,9 @@ public bool IsCompleted
public void OnCompleted(Action continuation) public void OnCompleted(Action continuation)
{ {
m_task.ContinueWith( m_task.ContinueWith(
delegate { continuation(); }, TaskAwaiter.TaskScheduler); delegate { continuation(); },
// I don't think continuing on the thread pool is what people really wanted when they called ConfigureAwait, but it's what the CTP did
m_useCapturedContext ? TaskAwaiter.TaskScheduler : TaskScheduler.Default);
} }


public T GetResult() public T GetResult()
Expand Down
10 changes: 10 additions & 0 deletions src/AsyncBridge/TaskUtils.cs
Expand Up @@ -56,6 +56,16 @@ public static YieldAwaitable Yield()
return new YieldAwaitable((object)SynchronizationContext.Current ?? TaskScheduler.Current); return new YieldAwaitable((object)SynchronizationContext.Current ?? TaskScheduler.Current);
} }


public static ConfigurableTaskAwaitable<T> ConfigureAwait<T>(this Task<T> original, bool continueOnCapturedContext)
{
return new ConfigurableTaskAwaitable<T>(original, continueOnCapturedContext);
}

public static ConfigurableTaskAwaitable ConfigureAwait(this Task original, bool continueOnCapturedContext)
{
return new ConfigurableTaskAwaitable(original, continueOnCapturedContext);
}

// Methods which are implemented in terms of TaskFactory // Methods which are implemented in terms of TaskFactory
public static Task<T[]> WhenAll<T>(params Task<T>[] tasks) public static Task<T[]> WhenAll<T>(params Task<T>[] tasks)
{ {
Expand Down
2 changes: 1 addition & 1 deletion src/AsyncBridge/YieldAwaitable.cs
Expand Up @@ -21,7 +21,7 @@ public YieldAwaiter GetAwaiter()
} }


[StructLayout(LayoutKind.Sequential, Size = 1)] [StructLayout(LayoutKind.Sequential, Size = 1)]
public struct YieldAwaiter : ICriticalNotifyCompletion public struct YieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion
{ {
private static readonly WaitCallback s_waitCallbackRunAction = runAction; private static readonly WaitCallback s_waitCallbackRunAction = runAction;
private static readonly SendOrPostCallback s_sendOrPostCallbackRunAction = private static readonly SendOrPostCallback s_sendOrPostCallbackRunAction =
Expand Down
8 changes: 7 additions & 1 deletion tests/AsyncBridge-Net35.Tests/AsyncBridge-Net35.Tests.csproj
Expand Up @@ -43,7 +43,13 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\AsyncBridge.Tests\*.cs" /> <Compile Include="..\AsyncBridge.Tests\DelayTest.cs" />
<Compile Include="..\AsyncBridge.Tests\SyncContextTests.cs" />
<Compile Include="..\AsyncBridge.Tests\Test.cs" />
<Compile Include="..\AsyncBridge.Tests\WhenAllTests.cs" />
<Compile Include="..\AsyncBridge.Tests\WhenAnyTests.cs" />
<Link>SyncContextTests.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions tests/AsyncBridge.Tests/AsyncBridge.Tests.csproj
Expand Up @@ -43,6 +43,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="DelayTest.cs" /> <Compile Include="DelayTest.cs" />
<Compile Include="SyncContextTests.cs" />
<Compile Include="Test.cs" /> <Compile Include="Test.cs" />
<Compile Include="WhenAllTests.cs" /> <Compile Include="WhenAllTests.cs" />
<Compile Include="WhenAnyTests.cs" /> <Compile Include="WhenAnyTests.cs" />
Expand Down
100 changes: 100 additions & 0 deletions tests/AsyncBridge.Tests/SyncContextTests.cs
@@ -0,0 +1,100 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AsyncBridge.Tests
{
[TestClass]
public class SyncContextTests
{
class MagicSynchronizationContext : SynchronizationContext
{
public static readonly MagicSynchronizationContext Instance = new MagicSynchronizationContext();

public override void Post(SendOrPostCallback d, object state)
{
base.Post(o =>
{
SetSynchronizationContext(this);
d(o);
}, state);
}
}

[TestMethod]
public async Task YieldSyncContext()
{
SynchronizationContext.SetSynchronizationContext(MagicSynchronizationContext.Instance);
await TaskUtils.Yield();
Assert.IsTrue(SynchronizationContext.Current is MagicSynchronizationContext);
}

[TestMethod]
public async Task FromResultSyncContext()
{
SynchronizationContext.SetSynchronizationContext(MagicSynchronizationContext.Instance);
int r = await TaskUtils.FromResult(4);
Assert.IsTrue(SynchronizationContext.Current is MagicSynchronizationContext);
Assert.AreEqual(4, r);
}

[TestMethod]
public async Task DelaySyncContext()
{
SynchronizationContext.SetSynchronizationContext(MagicSynchronizationContext.Instance);
await TaskUtils.Delay(1);
Assert.IsTrue(SynchronizationContext.Current is MagicSynchronizationContext);
}

[TestMethod]
public async Task SimpleTaskSyncContext()
{
SynchronizationContext.SetSynchronizationContext(MagicSynchronizationContext.Instance);
await WaitABit();
Assert.IsTrue(SynchronizationContext.Current is MagicSynchronizationContext);
}

[TestMethod]
public async Task ReturningTaskSyncContext()
{
SynchronizationContext.SetSynchronizationContext(MagicSynchronizationContext.Instance);
int r = await WaitAThing();
Assert.IsTrue(SynchronizationContext.Current is MagicSynchronizationContext);
Assert.AreEqual(6, r);
}

[TestMethod]
public async Task ConfiguredSimpleTaskSyncContext()
{
SynchronizationContext.SetSynchronizationContext(MagicSynchronizationContext.Instance);
await WaitABit().ConfigureAwait(false);
Assert.IsFalse(SynchronizationContext.Current is MagicSynchronizationContext);
}

[TestMethod]
public async Task ConfiguredReturningTaskSyncContext()
{
SynchronizationContext.SetSynchronizationContext(MagicSynchronizationContext.Instance);
int r = await WaitAThing().ConfigureAwait(false);
Assert.IsFalse(SynchronizationContext.Current is MagicSynchronizationContext);
Assert.AreEqual(6, r);
}

/// <summary>
/// Exercise our AsyncTaskMethodBuilder
/// </summary>
private async Task WaitABit()
{
await TaskUtils.Delay(1);
}

/// <summary>
/// Exercise our AsyncTaskMethodBuilder'1
/// </summary>
private async Task<int> WaitAThing()
{
await TaskUtils.Delay(1);
return await TaskUtils.FromResult(6);
}
}
}

0 comments on commit efaf223

Please sign in to comment.