-
Notifications
You must be signed in to change notification settings - Fork 1
/
AsyncUtils.cs
207 lines (193 loc) · 6.63 KB
/
AsyncUtils.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Microsoft.Unity
{
/// <summary>
/// Wraps a coroutine in an <see cref="IEnumerator"/> that handles exceptions in a safe way.
/// </summary>
/// <typeparam name="TResult">
/// The type of data returned by the coroutine.
/// </typeparam>
public class ExceptionSafeRoutine<TResult> : IEnumerator
{
#region Member Variables
private IEnumerator coroutine;
private TaskCompletionSource<TResult> exceptionSource;
#endregion // Member Variables
/// <summary>
/// Initializes a new <see cref="ExceptionSafeRoutine"/>.
/// </summary>
/// <param name="coroutine">
/// The coroutine to wrap.
/// </param>
/// <param name="exceptionSource">
/// The <see cref="TaskCompletionSource{TResult}"/> that will handle exceptions.
/// </param>
public ExceptionSafeRoutine(IEnumerator coroutine, TaskCompletionSource<TResult> exceptionSource)
{
// Validate
if (coroutine == null) throw new ArgumentNullException(nameof(coroutine));
if (exceptionSource == null) throw new ArgumentNullException(nameof(exceptionSource));
// Store
this.coroutine = coroutine;
this.exceptionSource = exceptionSource;
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public object Current
{
get
{
try
{
return coroutine.Current;
}
catch (Exception ex)
{
exceptionSource.TrySetException(ex);
return null;
}
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public bool MoveNext()
{
try
{
return coroutine.MoveNext();
}
catch (Exception ex)
{
exceptionSource.TrySetException(ex);
return false;
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Reset()
{
try
{
coroutine.Reset();
}
catch (Exception ex)
{
exceptionSource.TrySetException(ex);
}
}
}
/// <summary>
/// Utilities and extension methods for helping with async operations.
/// </summary>
static public class AsyncUtils
{
#region Constants
/// <summary>
/// The delegate for a handler of exceptions raised by a behavior.
/// </summary>
/// <param name="behaviour">
/// The behavior that raised the exception.
/// </param>
/// <param name="ex">
/// The exception.
/// </param>
public delegate void BehaviourErrorHandler(Behaviour behaviour, Exception ex);
#endregion // Constants
#region Internal Methods
/// <summary>
/// Logs the exception and disables the behavior.
/// </summary>
/// <param name="behaviour">
/// The behavior to disable.
/// </param>
/// <param name="ex">
/// The exception to log.
/// </param>
static private void LogError(Behaviour behaviour, Exception ex, bool disable)
{
// Get names
var behaviourName = behaviour.GetType().Name;
var objName = behaviour.gameObject.name;
// Log information
if (disable)
{
Debug.LogError($"{ex.Message} -- Async void error. {behaviourName} on {objName} has been disabled.");
behaviour.enabled = false;
}
else
{
Debug.LogError($"{ex.Message} -- Async void error ({behaviourName} on {objName}).");
}
}
#endregion // Internal Methods
#region Public Methods
/// <summary>
/// Runs a task as async void but passes any exception to the specified handler.
/// </summary>
/// <param name="behaviour">
/// The <see cref="Behaviour"/> which is requesting the action to be run async void.
/// </param>
/// <param name="task">
/// The function which yields the <see cref="Task"/> to perform.
/// </param>
/// <param name="onError">
/// A delegate of type <see cref="BehaviourErrorHandler"/> which will handle the error.
/// </param>
static public async void RunSafeVoid(this Behaviour behaviour, Func<Task> task, BehaviourErrorHandler onError)
{
// Run the task and await for any exception
try
{
await task();
}
catch (Exception ex)
{
onError(behaviour, ex);
}
}
/// <summary>
/// Runs a task as async void and logs any error that occurs.
/// </summary>
/// <param name="behaviour">
/// The <see cref="Behaviour"/> which is requesting the action to be run async void.
/// </param>
/// <param name="task">
/// The function which yields the <see cref="Task"/> to perform.
/// </param>
/// <param name="disableOnError">
/// If <c>true</c>, the behavior will be disabled if any error occurs.
/// </param>
static public void RunSafeVoid(this Behaviour behaviour, Func<Task> task, bool disableOnError = true)
{
RunSafeVoid(behaviour, task, (b, ex) => LogError(b, ex, disableOnError));
}
/// <summary>
/// Wraps a coroutine with exception handling that will ensure the completion source receives the exception.
/// </summary>
/// <typeparam name="TResult">
/// The type of result.
/// </typeparam>
/// <param name="coroutine">
/// The coroutine to wrap.
/// </param>
/// <param name="exceptionSource">
/// The <see cref="TaskCompletionSource{TResult}"/> that will handle exceptions.
/// </param>
/// <returns></returns>
static public IEnumerator WithExceptionHandling<TResult>(this IEnumerator coroutine, TaskCompletionSource<TResult> exceptionSource)
{
return new ExceptionSafeRoutine<TResult>(coroutine, exceptionSource);
}
#endregion // Public Methods
}
}