@@ -42,6 +42,18 @@ | |||||
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> | <HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> | ||||
<Private>True</Private> | <Private>True</Private> | ||||
</Reference> | </Reference> | ||||
<Reference Include="Nito.AsyncEx, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="Nito.AsyncEx.Concurrent, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="Nito.AsyncEx.Enlightenment, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System" /> | <Reference Include="System" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -1,4 +1,5 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
<packages> | <packages> | ||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> | <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> | ||||
<package id="Nito.AsyncEx" version="3.0.1" targetFramework="net45" /> | |||||
</packages> | </packages> |
@@ -2,6 +2,7 @@ | |||||
using Discord.Logging; | using Discord.Logging; | ||||
using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using Nito.AsyncEx; | |||||
using System; | using System; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -10,7 +11,7 @@ namespace Discord.Audio | |||||
{ | { | ||||
internal class AudioClient : IAudioClient | internal class AudioClient : IAudioClient | ||||
{ | { | ||||
private readonly Semaphore _connectionLock; | |||||
private readonly AsyncLock _connectionLock; | |||||
private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
private CancellationTokenSource _cancelTokenSource; | private CancellationTokenSource _cancelTokenSource; | ||||
@@ -31,7 +32,7 @@ namespace Discord.Audio | |||||
GatewaySocket = gatewaySocket; | GatewaySocket = gatewaySocket; | ||||
Logger = logger; | Logger = logger; | ||||
_connectionLock = new Semaphore(1, 1); | |||||
_connectionLock = new AsyncLock(); | |||||
_serializer = new JsonSerializer(); | _serializer = new JsonSerializer(); | ||||
_serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; | _serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; | ||||
@@ -92,8 +93,7 @@ namespace Discord.Audio | |||||
if (VoiceSocket.Server == null) | if (VoiceSocket.Server == null) | ||||
throw new InvalidOperationException("This client has been closed."); | throw new InvalidOperationException("This client has been closed."); | ||||
_connectionLock.WaitOne(); | |||||
try | |||||
using (await _connectionLock.LockAsync()) | |||||
{ | { | ||||
_cancelTokenSource = new CancellationTokenSource(); | _cancelTokenSource = new CancellationTokenSource(); | ||||
var cancelToken = _cancelTokenSource.Token; | var cancelToken = _cancelTokenSource.Token; | ||||
@@ -106,26 +106,17 @@ namespace Discord.Audio | |||||
VoiceSocket.WaitForConnection(cancelToken); | VoiceSocket.WaitForConnection(cancelToken); | ||||
}); | }); | ||||
} | } | ||||
finally | |||||
{ | |||||
_connectionLock.Release(); | |||||
} | |||||
} | } | ||||
public async Task Disconnect() | public async Task Disconnect() | ||||
{ | { | ||||
_connectionLock.WaitOne(); | |||||
try | |||||
using (await _connectionLock.LockAsync()) | |||||
{ | { | ||||
Service.RemoveClient(VoiceSocket.Server, this); | Service.RemoveClient(VoiceSocket.Server, this); | ||||
VoiceSocket.Channel = null; | VoiceSocket.Channel = null; | ||||
SendVoiceUpdate(); | SendVoiceUpdate(); | ||||
await VoiceSocket.Disconnect(); | await VoiceSocket.Disconnect(); | ||||
} | } | ||||
finally | |||||
{ | |||||
_connectionLock.Release(); | |||||
} | |||||
} | } | ||||
private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) | private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) | ||||
@@ -29,7 +29,7 @@ namespace Discord.Audio | |||||
public bool EnableMultiserver { get { return _enableMultiserver; } set { SetValue(ref _enableMultiserver, value); } } | public bool EnableMultiserver { get { return _enableMultiserver; } set { SetValue(ref _enableMultiserver, value); } } | ||||
private bool _enableMultiserver = false; | private bool _enableMultiserver = false; | ||||
/// <summary> Gets or sets the max buffer length (in milliseconds) for outgoing voice packets. This value is the target maximum but is not guaranteed, the buffer will often go slightly above this value. </summary> | |||||
/// <summary> Gets or sets the max buffer length (in milliseconds) for outgoing voice packets. </summary> | |||||
public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } } | public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } } | ||||
private int _bufferLength = 1000; | private int _bufferLength = 1000; | ||||
@@ -29,7 +29,7 @@ namespace Discord.Net.WebSockets | |||||
private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders; | private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders; | ||||
private readonly AudioClient _audioClient; | private readonly AudioClient _audioClient; | ||||
private readonly AudioServiceConfig _config; | private readonly AudioServiceConfig _config; | ||||
private Thread _sendThread, _receiveThread; | |||||
private Task _sendTask, _receiveTask; | |||||
private VoiceBuffer _sendBuffer; | private VoiceBuffer _sendBuffer; | ||||
private OpusEncoder _encoder; | private OpusEncoder _encoder; | ||||
private uint _ssrc; | private uint _ssrc; | ||||
@@ -95,20 +95,9 @@ namespace Discord.Net.WebSockets | |||||
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | ||||
List<Task> tasks = new List<Task>(); | List<Task> tasks = new List<Task>(); | ||||
if ((_config.Mode & AudioMode.Outgoing) != 0) | |||||
{ | |||||
_sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(CancelToken))); | |||||
_sendThread.IsBackground = true; | |||||
_sendThread.Start(); | |||||
} | |||||
/*if ((_config.Mode & AudioMode.Incoming) != 0) | |||||
{*/ | |||||
_receiveThread = new Thread(new ThreadStart(() => ReceiveVoiceAsync(CancelToken))); | |||||
_receiveThread.IsBackground = true; | |||||
_receiveThread.Start(); | |||||
/*} | |||||
else | |||||
tasks.Add(Task.Run(() => ReceiveVoiceAsync(CancelToken)));*/ | |||||
if (_config.Mode.HasFlag(AudioMode.Outgoing)) | |||||
_sendTask = Task.Run(() => SendVoiceAsync(CancelToken)); | |||||
_receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken)); | |||||
SendIdentify(); | SendIdentify(); | ||||
@@ -119,14 +108,15 @@ namespace Discord.Net.WebSockets | |||||
tasks.Add(HeartbeatAsync(CancelToken)); | tasks.Add(HeartbeatAsync(CancelToken)); | ||||
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); | await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); | ||||
} | } | ||||
protected override Task Cleanup() | |||||
protected override async Task Cleanup() | |||||
{ | { | ||||
if (_sendThread != null) | |||||
_sendThread.Join(); | |||||
if (_receiveThread != null) | |||||
_receiveThread.Join(); | |||||
_sendThread = null; | |||||
_receiveThread = null; | |||||
var sendThread = _sendTask; | |||||
if (sendThread != null) await sendThread; | |||||
_sendTask = null; | |||||
var receiveThread = _receiveTask; | |||||
if (receiveThread != null) await receiveThread; | |||||
_receiveTask = null; | |||||
OpusDecoder decoder; | OpusDecoder decoder; | ||||
foreach (var pair in _decoders) | foreach (var pair in _decoders) | ||||
@@ -138,10 +128,10 @@ namespace Discord.Net.WebSockets | |||||
ClearPCMFrames(); | ClearPCMFrames(); | ||||
_udp = null; | _udp = null; | ||||
return base.Cleanup(); | |||||
await base.Cleanup(); | |||||
} | } | ||||
private void ReceiveVoiceAsync(CancellationToken cancelToken) | |||||
private async Task ReceiveVoiceAsync(CancellationToken cancelToken) | |||||
{ | { | ||||
var closeTask = cancelToken.Wait(); | var closeTask = cancelToken.Wait(); | ||||
try | try | ||||
@@ -158,7 +148,7 @@ namespace Discord.Net.WebSockets | |||||
while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
{ | { | ||||
Thread.Sleep(1); | |||||
await Task.Delay(1); | |||||
if (_udp.Available > 0) | if (_udp.Available > 0) | ||||
{ | { | ||||
#if !DOTNET5_4 | #if !DOTNET5_4 | ||||
@@ -243,12 +233,12 @@ namespace Discord.Net.WebSockets | |||||
catch (InvalidOperationException) { } //Includes ObjectDisposedException | catch (InvalidOperationException) { } //Includes ObjectDisposedException | ||||
} | } | ||||
private void SendVoiceAsync(CancellationToken cancelToken) | |||||
private async Task SendVoiceAsync(CancellationToken cancelToken) | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected) | while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected) | ||||
Thread.Sleep(1); | |||||
await Task.Delay(1); | |||||
if (cancelToken.IsCancellationRequested) | if (cancelToken.IsCancellationRequested) | ||||
return; | return; | ||||
@@ -370,10 +360,10 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); | int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); | ||||
if (time > 0) | if (time > 0) | ||||
Thread.Sleep(time); | |||||
await Task.Delay(time); | |||||
} | } | ||||
else | else | ||||
Thread.Sleep(1); //Give as much time to the encrypter as possible | |||||
await Task.Delay(1); //Give as much time to the encrypter as possible | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
using Discord.Logging; | using Discord.Logging; | ||||
using Nito.AsyncEx; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -27,21 +28,20 @@ namespace Discord.Audio | |||||
void IAudioClient.Wait() => _client.Wait(); | void IAudioClient.Wait() => _client.Wait(); | ||||
} | } | ||||
private readonly Semaphore _connectionLock; | |||||
private readonly AsyncLock _connectionLock; | |||||
internal VirtualClient CurrentClient { get; private set; } | internal VirtualClient CurrentClient { get; private set; } | ||||
public SimpleAudioClient(AudioService service, int id, Logger logger) | public SimpleAudioClient(AudioService service, int id, Logger logger) | ||||
: base(service, id, null, service.Client.GatewaySocket, logger) | : base(service, id, null, service.Client.GatewaySocket, logger) | ||||
{ | { | ||||
_connectionLock = new Semaphore(1, 1); | |||||
_connectionLock = new AsyncLock(); | |||||
} | } | ||||
//Only disconnects if is current a member of this server | //Only disconnects if is current a member of this server | ||||
public async Task Leave(VirtualClient client) | public async Task Leave(VirtualClient client) | ||||
{ | { | ||||
_connectionLock.WaitOne(); | |||||
try | |||||
using (await _connectionLock.LockAsync()) | |||||
{ | { | ||||
if (CurrentClient == client) | if (CurrentClient == client) | ||||
{ | { | ||||
@@ -49,16 +49,11 @@ namespace Discord.Audio | |||||
await Disconnect(); | await Disconnect(); | ||||
} | } | ||||
} | } | ||||
finally | |||||
{ | |||||
_connectionLock.Release(); | |||||
} | |||||
} | } | ||||
internal async Task<IAudioClient> Connect(Channel channel) | internal async Task<IAudioClient> Connect(Channel channel) | ||||
{ | { | ||||
_connectionLock.WaitOne(); | |||||
try | |||||
using (await _connectionLock.LockAsync()) | |||||
{ | { | ||||
bool changeServer = channel.Server != VoiceSocket.Server; | bool changeServer = channel.Server != VoiceSocket.Server; | ||||
if (changeServer || CurrentClient == null) | if (changeServer || CurrentClient == null) | ||||
@@ -70,10 +65,6 @@ namespace Discord.Audio | |||||
await Join(channel); | await Join(channel); | ||||
return CurrentClient; | return CurrentClient; | ||||
} | } | ||||
finally | |||||
{ | |||||
_connectionLock.Release(); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -1,4 +1,5 @@ | |||||
using System; | |||||
using Nito.AsyncEx; | |||||
using System; | |||||
using System.Threading; | using System.Threading; | ||||
namespace Discord.Audio | namespace Discord.Audio | ||||
@@ -9,8 +10,9 @@ namespace Discord.Audio | |||||
private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
private readonly byte[] _blankFrame; | private readonly byte[] _blankFrame; | ||||
private ushort _readCursor, _writeCursor; | private ushort _readCursor, _writeCursor; | ||||
private ManualResetEventSlim _notOverflowEvent; | |||||
private ManualResetEventSlim _notOverflowEvent; | |||||
private bool _isClearing; | private bool _isClearing; | ||||
private AsyncLock _lock; | |||||
public int FrameSize => _frameSize; | public int FrameSize => _frameSize; | ||||
public int FrameCount => _frameCount; | public int FrameCount => _frameCount; | ||||
@@ -27,6 +29,7 @@ namespace Discord.Audio | |||||
_buffer = new byte[_bufferSize]; | _buffer = new byte[_bufferSize]; | ||||
_blankFrame = new byte[_frameSize]; | _blankFrame = new byte[_frameSize]; | ||||
_notOverflowEvent = new ManualResetEventSlim(); //Notifies when an overflow is solved | _notOverflowEvent = new ManualResetEventSlim(); //Notifies when an overflow is solved | ||||
_lock = new AsyncLock(); | |||||
} | } | ||||
public void Push(byte[] buffer, int bytes, CancellationToken cancelToken) | public void Push(byte[] buffer, int bytes, CancellationToken cancelToken) | ||||
@@ -38,8 +41,8 @@ namespace Discord.Audio | |||||
int expectedBytes = wholeFrames * _frameSize; | int expectedBytes = wholeFrames * _frameSize; | ||||
int lastFrameSize = bytes - expectedBytes; | int lastFrameSize = bytes - expectedBytes; | ||||
lock (this) | |||||
{ | |||||
using (_lock.Lock()) | |||||
{ | |||||
for (int i = 0, pos = 0; i <= wholeFrames; i++, pos += _frameSize) | for (int i = 0, pos = 0; i <= wholeFrames; i++, pos += _frameSize) | ||||
{ | { | ||||
//If the read cursor is in the next position, wait for it to move. | //If the read cursor is in the next position, wait for it to move. | ||||
@@ -83,35 +86,34 @@ namespace Discord.Audio | |||||
} | } | ||||
public bool Pop(byte[] buffer) | public bool Pop(byte[] buffer) | ||||
{ | |||||
if (_writeCursor == _readCursor) | |||||
{ | |||||
_notOverflowEvent.Set(); | |||||
return false; | |||||
} | |||||
{ | |||||
//using (_lock.Lock()) | |||||
//{ | |||||
if (_writeCursor == _readCursor) | |||||
{ | |||||
_notOverflowEvent.Set(); | |||||
return false; | |||||
} | |||||
bool isClearing = _isClearing; | |||||
if (!isClearing) | |||||
Buffer.BlockCopy(_buffer, _readCursor * _frameSize, buffer, 0, _frameSize); | |||||
bool isClearing = _isClearing; | |||||
if (!isClearing) | |||||
Buffer.BlockCopy(_buffer, _readCursor * _frameSize, buffer, 0, _frameSize); | |||||
//Advance the read cursor to the next position | |||||
AdvanceCursorPos(ref _readCursor); | |||||
_notOverflowEvent.Set(); | |||||
return !isClearing; | |||||
//Advance the read cursor to the next position | |||||
AdvanceCursorPos(ref _readCursor); | |||||
_notOverflowEvent.Set(); | |||||
return !isClearing; | |||||
//} | |||||
} | } | ||||
public void Clear(CancellationToken cancelToken) | public void Clear(CancellationToken cancelToken) | ||||
{ | { | ||||
lock (this) | |||||
{ | |||||
using (_lock.Lock()) | |||||
{ | |||||
_isClearing = true; | _isClearing = true; | ||||
for (int i = 0; i < _frameCount; i++) | for (int i = 0; i < _frameCount; i++) | ||||
Buffer.BlockCopy(_blankFrame, 0, _buffer, i * _frameCount, i++); | Buffer.BlockCopy(_blankFrame, 0, _buffer, i * _frameCount, i++); | ||||
try | |||||
{ | |||||
Wait(cancelToken); | |||||
} | |||||
catch (OperationCanceledException) { } | |||||
_writeCursor = 0; | _writeCursor = 0; | ||||
_readCursor = 0; | _readCursor = 0; | ||||
_isClearing = false; | _isClearing = false; | ||||
@@ -38,6 +38,18 @@ | |||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<Reference Include="Nito.AsyncEx, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="Nito.AsyncEx.Concurrent, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="Nito.AsyncEx.Enlightenment, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System" /> | <Reference Include="System" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -71,6 +83,9 @@ | |||||
<Name>Discord.Net</Name> | <Name>Discord.Net</Name> | ||||
</ProjectReference> | </ProjectReference> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<None Include="packages.config" /> | |||||
</ItemGroup> | |||||
<Import Project="..\Discord.Net.Shared\Discord.Net.Shared.projitems" Label="Shared" /> | <Import Project="..\Discord.Net.Shared\Discord.Net.Shared.projitems" Label="Shared" /> | ||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | ||||
@@ -0,0 +1,4 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<packages> | |||||
<package id="Nito.AsyncEx" version="3.0.1" targetFramework="net45" /> | |||||
</packages> |
@@ -1,4 +1,5 @@ | |||||
using Discord.Commands; | using Discord.Commands; | ||||
using Nito.AsyncEx; | |||||
using System; | using System; | ||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
@@ -48,8 +49,9 @@ namespace Discord.Modules | |||||
private readonly ConcurrentDictionary<ulong, Server> _enabledServers; | private readonly ConcurrentDictionary<ulong, Server> _enabledServers; | ||||
private readonly ConcurrentDictionary<ulong, Channel> _enabledChannels; | private readonly ConcurrentDictionary<ulong, Channel> _enabledChannels; | ||||
private readonly ConcurrentDictionary<ulong, int> _indirectServers; | private readonly ConcurrentDictionary<ulong, int> _indirectServers; | ||||
private readonly AsyncLock _lock; | |||||
public DiscordClient Client => _client; | |||||
public DiscordClient Client => _client; | |||||
public string Name => _name; | public string Name => _name; | ||||
public string Id => _id; | public string Id => _id; | ||||
public FilterType FilterType => _filterType; | public FilterType FilterType => _filterType; | ||||
@@ -60,15 +62,17 @@ namespace Discord.Modules | |||||
{ | { | ||||
_client = client; | _client = client; | ||||
_name = name; | _name = name; | ||||
_id = name.ToLowerInvariant(); | _id = name.ToLowerInvariant(); | ||||
_lock = new AsyncLock(); | |||||
_filterType = filterType; | |||||
_filterType = filterType; | |||||
_allowAll = filterType == FilterType.Unrestricted; | _allowAll = filterType == FilterType.Unrestricted; | ||||
_useServerWhitelist = filterType.HasFlag(FilterType.ServerWhitelist); | _useServerWhitelist = filterType.HasFlag(FilterType.ServerWhitelist); | ||||
_useChannelWhitelist = filterType.HasFlag(FilterType.ChannelWhitelist); | _useChannelWhitelist = filterType.HasFlag(FilterType.ChannelWhitelist); | ||||
_allowPrivate = filterType.HasFlag(FilterType.AllowPrivate); | _allowPrivate = filterType.HasFlag(FilterType.AllowPrivate); | ||||
_enabledServers = new ConcurrentDictionary<ulong, Server>(); | |||||
_enabledServers = new ConcurrentDictionary<ulong, Server>(); | |||||
_enabledChannels = new ConcurrentDictionary<ulong, Channel>(); | _enabledChannels = new ConcurrentDictionary<ulong, Channel>(); | ||||
_indirectServers = new ConcurrentDictionary<ulong, int>(); | _indirectServers = new ConcurrentDictionary<ulong, int>(); | ||||
@@ -122,8 +126,8 @@ namespace Discord.Modules | |||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); | if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); | ||||
lock (this) | |||||
return EnableServerInternal(server); | |||||
using (_lock.Lock()) | |||||
return EnableServerInternal(server); | |||||
} | } | ||||
public void EnableServers(IEnumerable<Server> servers) | public void EnableServers(IEnumerable<Server> servers) | ||||
{ | { | ||||
@@ -131,8 +135,8 @@ namespace Discord.Modules | |||||
if (servers.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(servers)); | if (servers.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(servers)); | ||||
if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); | if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); | ||||
lock (this) | |||||
{ | |||||
using (_lock.Lock()) | |||||
{ | |||||
foreach (var server in servers) | foreach (var server in servers) | ||||
EnableServerInternal(server); | EnableServerInternal(server); | ||||
} | } | ||||
@@ -153,8 +157,8 @@ namespace Discord.Modules | |||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
if (!_useServerWhitelist) return false; | if (!_useServerWhitelist) return false; | ||||
lock (this) | |||||
{ | |||||
using (_lock.Lock()) | |||||
{ | |||||
if (_enabledServers.TryRemove(server.Id, out server)) | if (_enabledServers.TryRemove(server.Id, out server)) | ||||
{ | { | ||||
if (ServerDisabled != null) | if (ServerDisabled != null) | ||||
@@ -169,8 +173,8 @@ namespace Discord.Modules | |||||
if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); | if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); | ||||
if (!_useServerWhitelist) return; | if (!_useServerWhitelist) return; | ||||
lock (this) | |||||
{ | |||||
using (_lock.Lock()) | |||||
{ | |||||
if (ServerDisabled != null) | if (ServerDisabled != null) | ||||
{ | { | ||||
foreach (var server in _enabledServers) | foreach (var server in _enabledServers) | ||||
@@ -186,8 +190,8 @@ namespace Discord.Modules | |||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); | if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); | ||||
lock (this) | |||||
return EnableChannelInternal(channel); | |||||
using (_lock.Lock()) | |||||
return EnableChannelInternal(channel); | |||||
} | } | ||||
public void EnableChannels(IEnumerable<Channel> channels) | public void EnableChannels(IEnumerable<Channel> channels) | ||||
{ | { | ||||
@@ -195,8 +199,8 @@ namespace Discord.Modules | |||||
if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels)); | if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels)); | ||||
if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); | if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); | ||||
lock (this) | |||||
{ | |||||
using (_lock.Lock()) | |||||
{ | |||||
foreach (var channel in channels) | foreach (var channel in channels) | ||||
EnableChannelInternal(channel); | EnableChannelInternal(channel); | ||||
} | } | ||||
@@ -225,8 +229,8 @@ namespace Discord.Modules | |||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
if (!_useChannelWhitelist) return false; | if (!_useChannelWhitelist) return false; | ||||
lock (this) | |||||
{ | |||||
using (_lock.Lock()) | |||||
{ | |||||
Channel ignored; | Channel ignored; | ||||
if (_enabledChannels.TryRemove(channel.Id, out ignored)) | if (_enabledChannels.TryRemove(channel.Id, out ignored)) | ||||
{ | { | ||||
@@ -252,8 +256,8 @@ namespace Discord.Modules | |||||
{ | { | ||||
if (!_useChannelWhitelist) return; | if (!_useChannelWhitelist) return; | ||||
lock (this) | |||||
{ | |||||
using (_lock.Lock()) | |||||
{ | |||||
if (ChannelDisabled != null) | if (ChannelDisabled != null) | ||||
{ | { | ||||
foreach (var channel in _enabledChannels) | foreach (var channel in _enabledChannels) | ||||
@@ -58,6 +58,18 @@ | |||||
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> | <HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> | ||||
<Private>True</Private> | <Private>True</Private> | ||||
</Reference> | </Reference> | ||||
<Reference Include="Nito.AsyncEx, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="Nito.AsyncEx.Concurrent, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="Nito.AsyncEx.Enlightenment, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\..\DiscordBot\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL"> | <Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL"> | ||||
<HintPath>..\..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll</HintPath> | <HintPath>..\..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll</HintPath> | ||||
<Private>True</Private> | <Private>True</Private> | ||||
@@ -1,6 +1,7 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
<packages> | <packages> | ||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> | <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> | ||||
<package id="Nito.AsyncEx" version="3.0.1" targetFramework="net45" /> | |||||
<package id="RestSharp" version="105.2.3" targetFramework="net45" /> | <package id="RestSharp" version="105.2.3" targetFramework="net45" /> | ||||
<package id="WebSocket4Net" version="0.14.1" targetFramework="net45" /> | <package id="WebSocket4Net" version="0.14.1" targetFramework="net45" /> | ||||
</packages> | </packages> |
@@ -5,6 +5,7 @@ using Discord.Net; | |||||
using Discord.Net.Rest; | using Discord.Net.Rest; | ||||
using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using Nito.AsyncEx; | |||||
using System; | using System; | ||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
@@ -21,7 +22,7 @@ namespace Discord | |||||
/// <summary> Provides a connection to the DiscordApp service. </summary> | /// <summary> Provides a connection to the DiscordApp service. </summary> | ||||
public partial class DiscordClient | public partial class DiscordClient | ||||
{ | { | ||||
private readonly Semaphore _connectionLock; | |||||
private readonly AsyncLock _connectionLock; | |||||
private readonly ManualResetEvent _disconnectedEvent; | private readonly ManualResetEvent _disconnectedEvent; | ||||
private readonly ManualResetEventSlim _connectedEvent; | private readonly ManualResetEventSlim _connectedEvent; | ||||
private readonly TaskManager _taskManager; | private readonly TaskManager _taskManager; | ||||
@@ -88,7 +89,7 @@ namespace Discord | |||||
//Async | //Async | ||||
_taskManager = new TaskManager(Cleanup); | _taskManager = new TaskManager(Cleanup); | ||||
_connectionLock = new Semaphore(1, 1); | |||||
_connectionLock = new AsyncLock(); | |||||
_disconnectedEvent = new ManualResetEvent(true); | _disconnectedEvent = new ManualResetEvent(true); | ||||
_connectedEvent = new ManualResetEventSlim(false); | _connectedEvent = new ManualResetEventSlim(false); | ||||
CancelToken = new CancellationToken(true); | CancelToken = new CancellationToken(true); | ||||
@@ -157,8 +158,7 @@ namespace Discord | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
_connectionLock.WaitOne(); | |||||
try | |||||
using (await _connectionLock.LockAsync()) | |||||
{ | { | ||||
if (State != ConnectionState.Disconnected) | if (State != ConnectionState.Disconnected) | ||||
await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
@@ -182,10 +182,6 @@ namespace Discord | |||||
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); | await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); | ||||
GatewaySocket.WaitForConnection(CancelToken); | GatewaySocket.WaitForConnection(CancelToken); | ||||
} | } | ||||
finally | |||||
{ | |||||
_connectionLock.Release(); | |||||
} | |||||
} | } | ||||
catch (Exception ex) | catch (Exception ex) | ||||
{ | { | ||||
@@ -7,7 +7,7 @@ namespace Discord | |||||
private static readonly string[] _patterns; | private static readonly string[] _patterns; | ||||
private static readonly StringBuilder _builder; | private static readonly StringBuilder _builder; | ||||
static Format() | |||||
static Format() | |||||
{ | { | ||||
_patterns = new string[] { "__", "_", "**", "*", "~~", "```", "`"}; | _patterns = new string[] { "__", "_", "**", "*", "~~", "```", "`"}; | ||||
_builder = new StringBuilder(DiscordConfig.MaxMessageSize); | _builder = new StringBuilder(DiscordConfig.MaxMessageSize); | ||||
@@ -39,7 +39,7 @@ namespace Discord | |||||
public override int GetHashCode() | public override int GetHashCode() | ||||
=> unchecked(ServerId.GetHashCode() + UserId.GetHashCode() + 23); | => unchecked(ServerId.GetHashCode() + UserId.GetHashCode() + 23); | ||||
} | } | ||||
private VoiceState _voiceState; | private VoiceState _voiceState; | ||||
private DateTime? _lastOnline; | private DateTime? _lastOnline; | ||||
private ulong? _voiceChannelId; | private ulong? _voiceChannelId; | ||||
@@ -9,6 +9,7 @@ using System.Net.Http; | |||||
using System.Net; | using System.Net; | ||||
using System.Text; | using System.Text; | ||||
using System.Globalization; | using System.Globalization; | ||||
using Nito.AsyncEx; | |||||
namespace Discord.Net.Rest | namespace Discord.Net.Rest | ||||
{ | { | ||||
@@ -20,7 +21,7 @@ namespace Discord.Net.Rest | |||||
private readonly HttpClient _client; | private readonly HttpClient _client; | ||||
private readonly string _baseUrl; | private readonly string _baseUrl; | ||||
private readonly object _rateLimitLock; | |||||
private readonly AsyncLock _rateLimitLock; | |||||
private DateTime _rateLimitTime; | private DateTime _rateLimitTime; | ||||
internal Logger Logger { get; } | internal Logger Logger { get; } | ||||
@@ -31,7 +32,7 @@ namespace Discord.Net.Rest | |||||
_baseUrl = baseUrl; | _baseUrl = baseUrl; | ||||
Logger = logger; | Logger = logger; | ||||
_rateLimitLock = new object(); | |||||
_rateLimitLock = new AsyncLock(); | |||||
_client = new HttpClient(new HttpClientHandler | _client = new HttpClient(new HttpClientHandler | ||||
{ | { | ||||
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, | AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, | ||||
@@ -102,7 +103,7 @@ namespace Discord.Net.Rest | |||||
var now = DateTime.UtcNow; | var now = DateTime.UtcNow; | ||||
if (now >= _rateLimitTime) | if (now >= _rateLimitTime) | ||||
{ | { | ||||
lock (_rateLimitLock) | |||||
using (await _rateLimitLock.LockAsync()) | |||||
{ | { | ||||
if (now >= _rateLimitTime) | if (now >= _rateLimitTime) | ||||
{ | { | ||||
@@ -1,11 +1,13 @@ | |||||
#if !DOTNET5_4 | #if !DOTNET5_4 | ||||
using Discord.Logging; | using Discord.Logging; | ||||
using Nito.AsyncEx; | |||||
using RestSharp; | using RestSharp; | ||||
using System; | using System; | ||||
using System.IO; | using System.IO; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using RestSharpClient = RestSharp.RestClient; | |||||
namespace Discord.Net.Rest | namespace Discord.Net.Rest | ||||
{ | { | ||||
@@ -14,9 +16,9 @@ namespace Discord.Net.Rest | |||||
private const int HR_SECURECHANNELFAILED = -2146233079; | private const int HR_SECURECHANNELFAILED = -2146233079; | ||||
private readonly DiscordConfig _config; | private readonly DiscordConfig _config; | ||||
private readonly RestSharp.RestClient _client; | |||||
private readonly RestSharpClient _client; | |||||
private readonly object _rateLimitLock; | |||||
private readonly AsyncLock _rateLimitLock; | |||||
private DateTime _rateLimitTime; | private DateTime _rateLimitTime; | ||||
internal Logger Logger { get; } | internal Logger Logger { get; } | ||||
@@ -26,8 +28,8 @@ namespace Discord.Net.Rest | |||||
_config = config; | _config = config; | ||||
Logger = logger; | Logger = logger; | ||||
_rateLimitLock = new object(); | |||||
_client = new RestSharp.RestClient(baseUrl) | |||||
_rateLimitLock = new AsyncLock(); | |||||
_client = new RestSharpClient(baseUrl) | |||||
{ | { | ||||
PreAuthenticate = false, | PreAuthenticate = false, | ||||
ReadWriteTimeout = _config.RestTimeout, | ReadWriteTimeout = _config.RestTimeout, | ||||
@@ -90,7 +92,7 @@ namespace Discord.Net.Rest | |||||
var now = DateTime.UtcNow; | var now = DateTime.UtcNow; | ||||
if (now >= _rateLimitTime) | if (now >= _rateLimitTime) | ||||
{ | { | ||||
lock (_rateLimitLock) | |||||
using (await _rateLimitLock.LockAsync()) | |||||
{ | { | ||||
if (now >= _rateLimitTime) | if (now >= _rateLimitTime) | ||||
{ | { | ||||
@@ -1,6 +1,7 @@ | |||||
using Discord.API.Client; | using Discord.API.Client; | ||||
using Discord.Logging; | using Discord.Logging; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using Nito.AsyncEx; | |||||
using System; | using System; | ||||
using System.IO; | using System.IO; | ||||
using System.IO.Compression; | using System.IO.Compression; | ||||
@@ -11,7 +12,7 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
public abstract partial class WebSocket | public abstract partial class WebSocket | ||||
{ | { | ||||
private readonly Semaphore _lock; | |||||
private readonly AsyncLock _lock; | |||||
protected readonly IWebSocketEngine _engine; | protected readonly IWebSocketEngine _engine; | ||||
protected readonly DiscordClient _client; | protected readonly DiscordClient _client; | ||||
protected readonly ManualResetEventSlim _connectedEvent; | protected readonly ManualResetEventSlim _connectedEvent; | ||||
@@ -45,7 +46,7 @@ namespace Discord.Net.WebSockets | |||||
Logger = logger; | Logger = logger; | ||||
_serializer = serializer; | _serializer = serializer; | ||||
_lock = new Semaphore(1, 1); | |||||
_lock = new AsyncLock(); | |||||
_taskManager = new TaskManager(Cleanup); | _taskManager = new TaskManager(Cleanup); | ||||
CancelToken = new CancellationToken(true); | CancelToken = new CancellationToken(true); | ||||
_connectedEvent = new ManualResetEventSlim(false); | _connectedEvent = new ManualResetEventSlim(false); | ||||
@@ -74,8 +75,7 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
_lock.WaitOne(); | |||||
try | |||||
using (await _lock.LockAsync()) | |||||
{ | { | ||||
await _taskManager.Stop().ConfigureAwait(false); | await _taskManager.Stop().ConfigureAwait(false); | ||||
_taskManager.ClearException(); | _taskManager.ClearException(); | ||||
@@ -87,10 +87,6 @@ namespace Discord.Net.WebSockets | |||||
await _engine.Connect(Host, CancelToken).ConfigureAwait(false); | await _engine.Connect(Host, CancelToken).ConfigureAwait(false); | ||||
await Run().ConfigureAwait(false); | await Run().ConfigureAwait(false); | ||||
} | |||||
finally | |||||
{ | |||||
_lock.Release(); | |||||
} | } | ||||
} | } | ||||
catch (Exception ex) | catch (Exception ex) | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | |||||
using Nito.AsyncEx; | |||||
using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Runtime.ExceptionServices; | using System.Runtime.ExceptionServices; | ||||
@@ -10,7 +11,7 @@ namespace Discord | |||||
/// <summary> Helper class used to manage several tasks and keep them in sync. If any single task errors or stops, all other tasks will also be stopped. </summary> | /// <summary> Helper class used to manage several tasks and keep them in sync. If any single task errors or stops, all other tasks will also be stopped. </summary> | ||||
public sealed class TaskManager | public sealed class TaskManager | ||||
{ | { | ||||
private readonly object _lock; | |||||
private readonly AsyncLock _lock; | |||||
private readonly Func<Task> _stopAction; | private readonly Func<Task> _stopAction; | ||||
private CancellationTokenSource _cancelSource; | private CancellationTokenSource _cancelSource; | ||||
@@ -24,7 +25,7 @@ namespace Discord | |||||
internal TaskManager() | internal TaskManager() | ||||
{ | { | ||||
_lock = new object(); | |||||
_lock = new AsyncLock(); | |||||
} | } | ||||
public TaskManager(Action stopAction) | public TaskManager(Action stopAction) | ||||
: this() | : this() | ||||
@@ -45,7 +46,7 @@ namespace Discord | |||||
if (task != null) | if (task != null) | ||||
await Stop().ConfigureAwait(false); | await Stop().ConfigureAwait(false); | ||||
lock (_lock) | |||||
using (await _lock.LockAsync()) | |||||
{ | { | ||||
_cancelSource = cancelSource; | _cancelSource = cancelSource; | ||||
@@ -88,7 +89,7 @@ namespace Discord | |||||
public void SignalStop(bool isExpected = false) | public void SignalStop(bool isExpected = false) | ||||
{ | { | ||||
lock (_lock) | |||||
using (_lock.Lock()) | |||||
{ | { | ||||
if (isExpected) | if (isExpected) | ||||
_wasStopExpected = true; | _wasStopExpected = true; | ||||
@@ -100,28 +101,27 @@ namespace Discord | |||||
_cancelSource.Cancel(); | _cancelSource.Cancel(); | ||||
} | } | ||||
} | } | ||||
public Task Stop(bool isExpected = false) | |||||
public async Task Stop(bool isExpected = false) | |||||
{ | { | ||||
Task task; | Task task; | ||||
lock (_lock) | |||||
using (await _lock.LockAsync()) | |||||
{ | { | ||||
if (isExpected) | if (isExpected) | ||||
_wasStopExpected = true; | _wasStopExpected = true; | ||||
//Cache the task so we still have something to await if Cleanup is run really quickly | //Cache the task so we still have something to await if Cleanup is run really quickly | ||||
task = _task; | task = _task; | ||||
if (task == null) return TaskHelper.CompletedTask; //Are we running? | |||||
if (_cancelSource.IsCancellationRequested) return task; | |||||
if (task == null) return; //Are we running? | |||||
if (_cancelSource != null) | |||||
if (!_cancelSource.IsCancellationRequested && _cancelSource != null) | |||||
_cancelSource.Cancel(); | _cancelSource.Cancel(); | ||||
} | } | ||||
return task; | |||||
await task; | |||||
} | } | ||||
public void SignalError(Exception ex) | public void SignalError(Exception ex) | ||||
{ | { | ||||
lock (_lock) | |||||
using (_lock.Lock()) | |||||
{ | { | ||||
if (_stopReason != null) return; | if (_stopReason != null) return; | ||||
@@ -130,33 +130,34 @@ namespace Discord | |||||
_cancelSource.Cancel(); | _cancelSource.Cancel(); | ||||
} | } | ||||
} | } | ||||
public Task Error(Exception ex) | |||||
public async Task Error(Exception ex) | |||||
{ | { | ||||
Task task; | Task task; | ||||
lock (_lock) | |||||
using (await _lock.LockAsync()) | |||||
{ | { | ||||
if (_stopReason != null) return TaskHelper.CompletedTask; | |||||
if (_stopReason != null) return; | |||||
//Cache the task so we still have something to await if Cleanup is run really quickly | //Cache the task so we still have something to await if Cleanup is run really quickly | ||||
task = _task ?? TaskHelper.CompletedTask; | task = _task ?? TaskHelper.CompletedTask; | ||||
if (_cancelSource.IsCancellationRequested) return task; | |||||
_stopReason = ExceptionDispatchInfo.Capture(ex); | |||||
if (_cancelSource != null) | |||||
_cancelSource.Cancel(); | |||||
if (!_cancelSource.IsCancellationRequested) | |||||
{ | |||||
_stopReason = ExceptionDispatchInfo.Capture(ex); | |||||
if (_cancelSource != null) | |||||
_cancelSource.Cancel(); | |||||
} | |||||
} | } | ||||
return task; | |||||
await task; | |||||
} | } | ||||
/// <summary> Throws an exception if one was captured. </summary> | /// <summary> Throws an exception if one was captured. </summary> | ||||
public void ThrowException() | public void ThrowException() | ||||
{ | { | ||||
lock (_lock) | |||||
using (_lock.Lock()) | |||||
_stopReason?.Throw(); | _stopReason?.Throw(); | ||||
} | } | ||||
public void ClearException() | public void ClearException() | ||||
{ | { | ||||
lock (_lock) | |||||
using (_lock.Lock()) | |||||
{ | { | ||||
_stopReason = null; | _stopReason = null; | ||||
_wasStopExpected = false; | _wasStopExpected = false; | ||||
@@ -33,7 +33,8 @@ | |||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"Newtonsoft.Json": "7.0.1" | |||||
"Newtonsoft.Json": "7.0.1", | |||||
"Nito.AsyncEx": "3.0.1" | |||||
}, | }, | ||||
"frameworks": { | "frameworks": { | ||||
@@ -52,11 +53,14 @@ | |||||
"System.Runtime.InteropServices": "4.0.21-beta-23516", | "System.Runtime.InteropServices": "4.0.21-beta-23516", | ||||
"System.Security.Cryptography.Algorithms": "4.0.0-beta-23516", | "System.Security.Cryptography.Algorithms": "4.0.0-beta-23516", | ||||
"System.Text.RegularExpressions": "4.0.11-beta-23516", | "System.Text.RegularExpressions": "4.0.11-beta-23516", | ||||
"System.Threading": "4.0.11-beta-23516", | |||||
"System.Threading.Thread": "4.0.0-beta-23516" | |||||
"System.Threading": "4.0.11-beta-23516" | |||||
} | } | ||||
}, | }, | ||||
"net45": { | "net45": { | ||||
"frameworkAssemblies": { | |||||
"System.Runtime": "4.0.0.0", | |||||
"System.Threading.Tasks": "4.0.0.0" | |||||
}, | |||||
"dependencies": { | "dependencies": { | ||||
"WebSocket4Net": "0.14.1", | "WebSocket4Net": "0.14.1", | ||||
"RestSharp": "105.2.3" | "RestSharp": "105.2.3" | ||||