-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathWasmScheduler.cs
157 lines (134 loc) · 6.23 KB
/
WasmScheduler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright (c) 2019 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace System.Reactive.Concurrency
{
/// <summary>
/// A scheduler for the WASM systems.
/// </summary>
public class WasmScheduler : LocalScheduler, ISchedulerPeriodic
{
private static readonly Lazy<WasmScheduler> _default = new Lazy<WasmScheduler>(() => new WasmScheduler());
/// <summary>
/// Gets the singleton instance of the Windows Runtime thread pool scheduler.
/// </summary>
public static WasmScheduler Default => _default.Value;
/// <inheritdoc />
public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
SingleAssignmentDisposable d = new SingleAssignmentDisposable();
WasmRuntime.ScheduleTimeout(0, () =>
{
if (!d.IsDisposed)
{
d.Disposable = action(this, state);
}
});
return d;
}
/// <summary>
/// Schedules a periodic piece of work, using a Windows.System.Threading.ThreadPoolTimer object.
/// </summary>
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
/// <param name="state">Initial state passed to the action upon the first iteration.</param>
/// <param name="period">Period for running the work periodically.</param>
/// <param name="action">Action to be executed, potentially updating the state.</param>
/// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> is less than one millisecond.</exception>
public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
{
// The WinRT thread pool is based on the Win32 thread pool and cannot handle
// sub-1ms resolution. When passing a lower period, we get single-shot
// timer behavior instead. See MSDN documentation for CreatePeriodicTimer
// for more information.
if (period < TimeSpan.FromMilliseconds(1))
{
throw new ArgumentOutOfRangeException(nameof(period), "The WinRT thread pool doesn't support creating periodic timers with a period below 1 millisecond.");
}
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
TState state1 = state;
AsyncLock gate = new AsyncLock();
WasmRuntime.ScheduleTimeout(
(int)period.TotalMilliseconds,
() =>
{
void Run()
{
gate.Wait(() =>
{
state1 = action(state1);
WasmRuntime.ScheduleTimeout((int)period.TotalMilliseconds, Run);
});
}
});
return Disposable.Create(() =>
{
gate.Dispose();
action = Stubs<TState>.I;
});
}
/// <inheritdoc />
public override IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
TimeSpan dt = Scheduler.Normalize(dueTime);
if (dt.Ticks == 0)
{
return Schedule(state, action);
}
SingleAssignmentDisposable d = new SingleAssignmentDisposable();
WasmRuntime.ScheduleTimeout(
(int)dt.TotalMilliseconds,
() =>
{
if (!d.IsDisposed)
{
d.Disposable = action(this, state);
}
});
return d;
}
// Import from https://github.com/mono/mono/blob/0a8126c2094d2d0800a462d4d0c790d4db421477/mcs/class/corlib/System.Threading/Timer.cs#L39
internal static class WasmRuntime
{
private static readonly ScheduleTimeoutDelegate _scheduleTimeout;
static WasmRuntime()
{
// Note that the assembly name must be provided here for mono-wasm AOT to work properly, as
// there is no stack walking available to determine the resolution context.
if (Type.GetType("System.Threading.WasmRuntime, mscorlib") is Type wasmRuntime
&& wasmRuntime.GetMethod(nameof(ScheduleTimeout), Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Static) is MethodInfo scheduleTimeout)
{
_scheduleTimeout = (ScheduleTimeoutDelegate)scheduleTimeout.CreateDelegate(typeof(ScheduleTimeoutDelegate));
}
else
{
#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations. Removed for performance reasons.
throw new NotSupportedException("The currently running version of the runtime does not support this version of the WebAssembly scheduler.");
#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations
}
}
private delegate void ScheduleTimeoutDelegate(int timeout, Action action);
internal static void ScheduleTimeout(int timeout, Action action)
{
_scheduleTimeout.Invoke(timeout, action);
}
}
}
}