Skip to content

Commit 221d1ec

Browse files
author
msftbot[bot]
authored
Move Deferred helpers to Microsoft.Toolkit (#3762)
## Fixes #3707 Moves the Deferred Event Helpers to the main .NET package and leaves the Uwp specific extensions in the Microsoft.Toolkit.Uwp package. ## PR Type What kind of change does this PR introduce? <!-- Please uncomment one or more that apply to this PR. --> <!-- - Bugfix --> <!-- - Feature --> <!-- - Code style update (formatting) --> - Refactoring (no functional changes, no api changes) <!-- - Build or CI related changes --> <!-- - Documentation content changes --> <!-- - Sample app changes --> <!-- - Other... Please describe: --> ## What is the current behavior? Deferred events pattern only available in UWP. ## What is the new behavior? Deferred events pattern can be used in .NET ## PR Checklist Please check if your PR fulfills the following requirements: - [ ] Tested code with current [supported SDKs](../readme.md#supported) - [ ] Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: <!-- docs PR link --> - [ ] Sample in sample app has been added / updated (for bug fixes / features) - [ ] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets) - [ ] New major technical changes in the toolkit have or will be added to the [Wiki](https://github.com/windows-toolkit/WindowsCommunityToolkit/wiki) e.g. build changes, source generators, testing infrastructure, sample creation changes, etc... - [ ] Tests for the changes have been added (for bug fixes / features) (if applicable) - [ ] Header has been added to all new source files (run *build/UpdateHeaders.bat*) - [ ] Contains **NO** breaking changes <!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. Please note that breaking changes are likely to be rejected within minor release cycles or held until major versions. --> ## Other information
2 parents 4b92896 + c64f30e commit 221d1ec

File tree

10 files changed

+185
-14
lines changed

10 files changed

+185
-14
lines changed

Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenItemAddingEventArgs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using Microsoft.Toolkit.Uwp.Deferred;
5+
using Microsoft.Toolkit.Deferred;
66

77
namespace Microsoft.Toolkit.Uwp.UI.Controls
88
{

Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenItemRemovingEventArgs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using Microsoft.Toolkit.Uwp.Deferred;
5+
using Microsoft.Toolkit.Deferred;
66

77
namespace Microsoft.Toolkit.Uwp.UI.Controls
88
{

Microsoft.Toolkit.Uwp/Deferred/TypedEventHandlerExtensions.cs renamed to Microsoft.Toolkit.Uwp/Extensions/TypedEventHandlerExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.Toolkit.Deferred;
910
using Windows.Foundation;
1011

1112
namespace Microsoft.Toolkit.Uwp.Deferred
@@ -56,9 +57,11 @@ public static Task InvokeAsync<S, R>(this TypedEventHandler<S, R> eventHandler,
5657

5758
invocationDelegate(sender, eventArgs);
5859

60+
#pragma warning disable CS0618 // Type or member is obsolete
5961
var deferral = eventArgs.GetCurrentDeferralAndReset();
6062

6163
return deferral?.WaitForCompletion(cancellationToken) ?? Task.CompletedTask;
64+
#pragma warning restore CS0618 // Type or member is obsolete
6265
})
6366
.ToArray();
6467

Microsoft.Toolkit.Uwp/Deferred/DeferredCancelEventArgs.cs renamed to Microsoft.Toolkit/Deferred/DeferredCancelEventArgs.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
namespace Microsoft.Toolkit.Uwp.Deferred
5+
namespace Microsoft.Toolkit.Deferred
66
{
77
/// <summary>
88
/// <see cref="DeferredEventArgs"/> which can also be canceled.

Microsoft.Toolkit.Uwp/Deferred/DeferredEventArgs.cs renamed to Microsoft.Toolkit/Deferred/DeferredEventArgs.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.ComponentModel;
67

7-
namespace Microsoft.Toolkit.Uwp.Deferred
8+
#pragma warning disable CA1001
9+
10+
namespace Microsoft.Toolkit.Deferred
811
{
912
/// <summary>
1013
/// <see cref="EventArgs"/> which can retrieve a <see cref="EventDeferral"/> in order to process data asynchronously before an <see cref="EventHandler"/> completes and returns to the calling control.
@@ -18,7 +21,7 @@ public class DeferredEventArgs : EventArgs
1821

1922
private readonly object _eventDeferralLock = new object();
2023

21-
private EventDeferral _eventDeferral;
24+
private EventDeferral? _eventDeferral;
2225

2326
/// <summary>
2427
/// Returns an <see cref="EventDeferral"/> which can be completed when deferred event is ready to continue.
@@ -28,11 +31,21 @@ public EventDeferral GetDeferral()
2831
{
2932
lock (_eventDeferralLock)
3033
{
31-
return _eventDeferral ?? (_eventDeferral = new EventDeferral());
34+
return _eventDeferral ??= new EventDeferral();
3235
}
3336
}
3437

35-
internal EventDeferral GetCurrentDeferralAndReset()
38+
/// <summary>
39+
/// DO NOT USE - This is a support method used by <see cref="EventHandlerExtensions"/>. It is public only for
40+
/// additional usage within extensions for the UWP based TypedEventHandler extensions.
41+
/// </summary>
42+
/// <returns>Internal EventDeferral reference</returns>
43+
#if !NETSTANDARD1_4
44+
[Browsable(false)]
45+
#endif
46+
[EditorBrowsable(EditorBrowsableState.Never)]
47+
[Obsolete("This is an internal only method to be used by EventHandler extension classes, public callers should call GetDeferral() instead.")]
48+
public EventDeferral? GetCurrentDeferralAndReset()
3649
{
3750
lock (_eventDeferralLock)
3851
{
@@ -44,4 +57,4 @@ internal EventDeferral GetCurrentDeferralAndReset()
4457
}
4558
}
4659
}
47-
}
60+
}

Microsoft.Toolkit.Uwp/Deferred/EventDeferral.cs renamed to Microsoft.Toolkit/Deferred/EventDeferral.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.ComponentModel;
67
using System.Threading;
78
using System.Threading.Tasks;
89

9-
namespace Microsoft.Toolkit.Uwp.Deferred
10+
#pragma warning disable CA1063
11+
12+
namespace Microsoft.Toolkit.Deferred
1013
{
1114
/// <summary>
1215
/// Deferral handle provided by a <see cref="DeferredEventArgs"/>.
1316
/// </summary>
1417
public class EventDeferral : IDisposable
1518
{
16-
private readonly TaskCompletionSource<object> _taskCompletionSource = new TaskCompletionSource<object>();
19+
//// TODO: If/when .NET 5 is base, we can upgrade to non-generic version
20+
private readonly TaskCompletionSource<object?> _taskCompletionSource = new TaskCompletionSource<object?>();
1721

1822
internal EventDeferral()
1923
{
@@ -24,7 +28,17 @@ internal EventDeferral()
2428
/// </summary>
2529
public void Complete() => _taskCompletionSource.TrySetResult(null);
2630

27-
internal async Task WaitForCompletion(CancellationToken cancellationToken)
31+
/// <summary>
32+
/// Waits for the <see cref="EventDeferral"/> to be completed by the event handler.
33+
/// </summary>
34+
/// <param name="cancellationToken"><see cref="CancellationToken"/>.</param>
35+
/// <returns><see cref="Task"/>.</returns>
36+
#if !NETSTANDARD1_4
37+
[Browsable(false)]
38+
#endif
39+
[EditorBrowsable(EditorBrowsableState.Never)]
40+
[Obsolete("This is an internal only method to be used by EventHandler extension classes, public callers should call GetDeferral() instead on the DeferredEventArgs.")]
41+
public async Task WaitForCompletion(CancellationToken cancellationToken)
2842
{
2943
using (cancellationToken.Register(() => _taskCompletionSource.TrySetCanceled()))
3044
{
@@ -38,4 +52,4 @@ public void Dispose()
3852
Complete();
3953
}
4054
}
41-
}
55+
}

Microsoft.Toolkit.Uwp/Deferred/EventHandlerExtensions.cs renamed to Microsoft.Toolkit/Extensions/EventHandlerExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using System.Threading;
88
using System.Threading.Tasks;
99

10-
namespace Microsoft.Toolkit.Uwp.Deferred
10+
namespace Microsoft.Toolkit.Deferred
1111
{
1212
/// <summary>
1313
/// Extensions to <see cref="EventHandler{TEventArgs}"/> for Deferred Events.
@@ -53,9 +53,11 @@ public static Task InvokeAsync<T>(this EventHandler<T> eventHandler, object send
5353

5454
invocationDelegate(sender, eventArgs);
5555

56+
#pragma warning disable CS0618 // Type or member is obsolete
5657
var deferral = eventArgs.GetCurrentDeferralAndReset();
5758

5859
return deferral?.WaitForCompletion(cancellationToken) ?? Task.CompletedTask;
60+
#pragma warning restore CS0618 // Type or member is obsolete
5961
})
6062
.ToArray();
6163

ThirdPartyNotices.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Do Not Translate or Localize
55

66
This project incorporates components from the projects listed below. The original copyright notices and the licenses under which the .NET Foundation received such components are set forth below. The .NET Foundation reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise.
77

8-
1. PedroLamas/DeferredEvents version 1.0.4 (https://github.com/PedroLamas/DeferredEvents), included in Microsoft.Toolkit.Uwp/Deferred.
8+
1. PedroLamas/DeferredEvents version 1.0.4 (https://github.com/PedroLamas/DeferredEvents), included in Microsoft.Toolkit/Deferred.
99
2. MichaeIDietrich/UwpNotificationNetCoreTest commit 5c1a4a3 (https://github.com/MichaeIDietrich/UwpNotificationNetCoreTest), used in DesktopNotificationManagerCompat.cs to support .NET Core 3.0.
1010
3. lbugnion/mvvmlight commit 4cbf77c (https://github.com/lbugnion/mvvmlight), from which some APIs from the `Microsoft.Toolkit.Mvvm` package take inspiration from.
1111
4. PrivateObject/PrivateType (https://github.com/microsoft/testfx/tree/664ac7c2ac9dbfbee9d2a0ef560cfd72449dfe34/src/TestFramework/Extension.Desktop), included in UnitTests.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Threading.Tasks;
7+
using Microsoft.Toolkit.Deferred;
8+
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
10+
namespace UnitTests.Extensions
11+
{
12+
[TestClass]
13+
public class Test_EventHandlerExtensions
14+
{
15+
[TestCategory("Deferred")]
16+
[TestMethod]
17+
public void Test_EventHandlerExtensions_GettingDeferralCausesAwait()
18+
{
19+
var tsc = new TaskCompletionSource<bool>();
20+
21+
var testClass = new TestClass();
22+
23+
testClass.TestEvent += async (s, e) =>
24+
{
25+
var deferral = e.GetDeferral();
26+
27+
await tsc.Task;
28+
29+
deferral.Complete();
30+
};
31+
32+
var handlersTask = testClass.RaiseTestEvent();
33+
34+
Assert.IsFalse(handlersTask.IsCompleted);
35+
36+
tsc.SetResult(true);
37+
38+
Assert.IsTrue(handlersTask.IsCompleted);
39+
}
40+
41+
[TestCategory("Deferred")]
42+
[TestMethod]
43+
public void Test_EventHandlerExtensions_NotGettingDeferralCausesNoAwait()
44+
{
45+
var tsc = new TaskCompletionSource<bool>();
46+
47+
var testClass = new TestClass();
48+
49+
testClass.TestEvent += async (s, e) =>
50+
{
51+
await tsc.Task;
52+
};
53+
54+
var handlersTask = testClass.RaiseTestEvent();
55+
56+
Assert.IsTrue(handlersTask.IsCompleted);
57+
58+
tsc.SetResult(true);
59+
}
60+
61+
[TestCategory("Deferred")]
62+
[TestMethod]
63+
public void Test_EventHandlerExtensions_UsingDeferralCausesAwait()
64+
{
65+
var tsc = new TaskCompletionSource<bool>();
66+
67+
var testClass = new TestClass();
68+
69+
testClass.TestEvent += async (s, e) =>
70+
{
71+
using (e.GetDeferral())
72+
{
73+
await tsc.Task;
74+
}
75+
};
76+
77+
var handlersTask = testClass.RaiseTestEvent();
78+
79+
Assert.IsFalse(handlersTask.IsCompleted);
80+
81+
tsc.SetResult(true);
82+
83+
Assert.IsTrue(handlersTask.IsCompleted);
84+
}
85+
86+
[TestCategory("Deferred")]
87+
[DataTestMethod]
88+
[DataRow(0, 1)]
89+
[DataRow(1, 0)]
90+
public void Test_EventHandlerExtensions_MultipleHandlersCauseAwait(int firstToReleaseDeferral, int lastToReleaseDeferral)
91+
{
92+
var tsc = new[]
93+
{
94+
new TaskCompletionSource<bool>(),
95+
new TaskCompletionSource<bool>()
96+
};
97+
98+
var testClass = new TestClass();
99+
100+
testClass.TestEvent += async (s, e) =>
101+
{
102+
var deferral = e.GetDeferral();
103+
104+
await tsc[0].Task;
105+
106+
deferral.Complete();
107+
};
108+
109+
testClass.TestEvent += async (s, e) =>
110+
{
111+
var deferral = e.GetDeferral();
112+
113+
await tsc[1].Task;
114+
115+
deferral.Complete();
116+
};
117+
118+
var handlersTask = testClass.RaiseTestEvent();
119+
120+
Assert.IsFalse(handlersTask.IsCompleted);
121+
122+
tsc[firstToReleaseDeferral].SetResult(true);
123+
124+
Assert.IsFalse(handlersTask.IsCompleted);
125+
126+
tsc[lastToReleaseDeferral].SetResult(true);
127+
128+
Assert.IsTrue(handlersTask.IsCompleted);
129+
}
130+
131+
private class TestClass
132+
{
133+
public event EventHandler<DeferredEventArgs> TestEvent;
134+
135+
public Task RaiseTestEvent() => TestEvent.InvokeAsync(this, new DeferredEventArgs());
136+
}
137+
}
138+
}

UnitTests/UnitTests.Shared/UnitTests.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Test_Guard.Array.cs" />
2020
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Test_Guard.Comparable.Numeric.cs" />
2121
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Test_Guard.cs" />
22+
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_EventHandlerExtensions.cs" />
2223
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_TaskExtensions.cs" />
2324
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.cs" />
2425
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_TypeExtensions.cs" />

0 commit comments

Comments
 (0)