Is there a way to call SendAsync in OnConnect without leading to a deadlock? I'm not using .Wait or .Result and it still leads to a deadlock.
Edit:
The actual problem is that SendAsync is being called twice (once at OnConnect and once at Main). If I put a await Task.Delay(10000) before the second call in Main, it actually works good. How can I fix it? If there is no task delay, it basically hangs on await tcs.Task.ConfigureAwait(false), because it's being called twice and async void OnConnect is kinda "fire and forget", meaning that it's not waiting for the first SendAsync to complete, before it goes for the second call.
// Program.cs
var client = new Client(key, secret);
await client.StartAsync().ConfigureAwait(false);
await Task.Delay(3000); // This line fixes it, but it's kinda fake fix
await client.SendAsync(request).ConfigureAwait(false);
await client.SendAsync(request2).ConfigureAwait(false);
Console.ReadLine();
// Client.cs
public class Client
{
private static long _nextId;
private readonly WebSocketClient _webSocket;
private readonly ConcurrentDictionary<long, TaskCompletionSource<string>> _outstandingRequests = new();
...
public event EventHandler<ConnectEventArgs>? Connected;
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public ValueTask StartAsync()
{
_client.Connected += OnConnect;
_client.MessageReceived += OnMessageReceived;
return _webSocket.StartAsync(); // there is a long-running `Task.Run` inside it, which keeps the web socket connection and its pipelines open.
}
private async void OnConnect(object? sender, ConnectEventArgs e)
{
await AuthAsync(...); // the problematic line
}
private void OnMessageReceived(object? sender, MessageReceivedEventArgs e)
{
... deserialization stuff
if (_requests.TryRemove(response.Id, out var tcs))
{
tcs.TrySetResult(message);
}
}
public ValueTask<TResponse?> SendAsync<TResponse>(JsonRpcRequest request)
{
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async ValueTask<TResponse?> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message).ConfigureAwait(false);
var response = await tcs.Task.ConfigureAwait(false); // it hangs here (deadlock)
return JsonSerializer.Deserialize<TResponse>(response);
}
}
public ValueTask<JsonRpcResponse?> AuthAsync(JsonRpcRequest request)
{
return SendAsync<JsonRpcResponse>(request);
}
private static long NextId()
{
return Interlocked.Increment(ref _nextId);
}
}
public sealed class WebSocketClient
{
private readonly AsyncManualResetEvent _sendSemaphore = new(false); // Nito.AsyncEx
private readonly WebSocketPipe _webSocket; // SignalR Web Socket Pipe
...
public event EventHandler<ConnectEventArgs>? Connected;
public event EventHandler<DisconnectEventArgs>? Disconnected;
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public ValueTask StartAsync()
{
_ = Task.Run(async () =>
{
try
{
await CreatePolicy()
.ExecuteAsync(async () =>
{
await _webSocket.StartAsync(new Uri(_url), CancellationToken.None).ConfigureAwait(false);
Connected?.Invoke(this, new ConnectEventArgs());
_sendSemaphore.Set();
await ReceiveLoopAsync().ConfigureAwait(false);
})
.ConfigureAwait(false);
}
catch (Exception ex)
{
// Failed after all retries
Disconnected?.Invoke(this, new DisconnectEventArgs(ex));
}
});
return ValueTask.CompletedTask;
}
public async ValueTask SendAsync(string message)
{
await _sendSemaphore.WaitAsync().ConfigureAwait(false);
var encoded = Encoding.UTF8.GetBytes(message);
await _webSocket.Transport!.Output
.WriteAsync(new ArraySegment<byte>(encoded, 0, encoded.Length), CancellationToken.None)
.ConfigureAwait(false);
}
private IAsyncPolicy CreatePolicy()
{
var retryPolicy = Policy
.Handle<WebSocketException>()
.WaitAndRetryForeverAsync(_ => ReconnectInterval,
(exception, retryCount, calculatedWaitDuration) =>
{
_sendSemaphore.Reset();
Reconnecting?.Invoke(this, new ReconnectingEventArgs(exception, retryCount, calculatedWaitDuration));
return Task.CompletedTask;
});
return retryPolicy;
}
private async Task ReceiveLoopAsync()
{
while (true)
{
var result = await _webSocket.Transport!.Input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
var buffer = result.Buffer;
...
}
}
}
As mentioned in the comments, those web socket wrappers are using System.IO.Pipelines, which is incorrect. System.IO.Pipelines is a stream of bytes, so it's appropriate for (non-web) sockets; a web socket is a stream of messages, so something like System.Threading.Channels would be more appropriate.
You could try something like this, which I just typed up and haven't even run:
public sealed class ChannelWebSocket : IDisposable
{
private readonly WebSocket _webSocket;
private readonly Channel<Message> _input;
private readonly Channel<Message> _output;
public ChannelWebSocket(WebSocket webSocket, Options options)
{
_webSocket = webSocket;
_input = Channel.CreateBounded(new BoundedChannelOptions(options.InputCapacity)
{
FullMode = options.InputFullMode,
}, options.InputMessageDropped);
_output = Channel.CreateBounded(new BoundedChannelOptions(options.OutputCapacity)
{
FullMode = options.OutputFullMode,
}, options.OutputMessageDropped);
}
public ChannelReader<Message> Input => _input.Reader;
public ChannelWriter<Message> Output => _output.Writer;
public void Dispose() => _webSocket.Dispose();
public async void Start()
{
var inputTask = InputLoopAsync(default);
var outputTask = OutputLoopAsync(default);
var completedTask = await Task.WhenAny(inputTask, outputTask);
if (completedTask.Exception != null)
{
try { await _webSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, statusDescription: null, default); } catch { /* ignore */ }
try { _input.Writer.Complete(completedTask.Exception); } catch { /* ignore */ }
try { _output.Writer.Complete(completedTask.Exception); } catch { /* ignore */ }
}
}
public sealed class Message
{
public WebSocketMessageType MessageType { get; set; }
public OwnedMemorySequence<byte> Payload { get; set; } = null!;
}
private async Task InputLoopAsync(CancellationToken cancellationToken)
{
while (true)
{
var payload = new OwnedMemorySequence<byte>();
var buffer = MemoryPool<byte>.Shared.Rent();
ValueWebSocketReceiveResult result;
do
{
result = await _webSocket.ReceiveAsync(buffer.Memory, cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
_input.Writer.Complete();
return;
}
payload.Append(buffer.Slice(0, result.Count));
} while (!result.EndOfMessage);
await _input.Writer.WriteAsync(new Message
{
MessageType = result.MessageType,
Payload = payload,
}, cancellationToken);
}
}
private async Task OutputLoopAsync(CancellationToken cancellationToken)
{
await foreach (var message in _output.Reader.ReadAllAsync())
{
var sequence = message.Payload.ReadOnlySequence;
if (sequence.IsEmpty)
continue;
while (!sequence.IsSingleSegment)
{
await _webSocket.SendAsync(sequence.First, message.MessageType, endOfMessage: false, cancellationToken);
sequence = sequence.Slice(sequence.First.Length);
}
await _webSocket.SendAsync(sequence.First, message.MessageType, endOfMessage: true, cancellationToken);
message.Payload.Dispose();
}
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, statusDescription: null, cancellationToken);
}
public sealed class Options
{
public int InputCapacity { get; set; } = 16;
public BoundedChannelFullMode InputFullMode { get; set; } = BoundedChannelFullMode.Wait;
public Action<Message>? InputMessageDropped { get; set; }
public int OutputCapacity { get; set; } = 16;
public BoundedChannelFullMode OutputFullMode { get; set; } = BoundedChannelFullMode.Wait;
public Action<Message>? OutputMessageDropped { get; set; }
}
}
It uses this type for building a memory sequence:
public sealed class MemorySequence<T>
{
private MemorySegment? _head;
private MemorySegment? _tail;
public MemorySequence<T> Append(ReadOnlyMemory<T> buffer)
{
if (_tail == null)
_head = _tail = new MemorySegment(buffer, runningIndex: 0);
else
_tail = _tail.Append(buffer);
return this;
}
public ReadOnlySequence<T> ReadOnlySequence => CreateReadOnlySequence(0, _tail?.Memory.Length ?? 0);
public ReadOnlySequence<T> CreateReadOnlySequence(int firstBufferStartIndex, int lastBufferEndIndex) =>
_tail == null ? new ReadOnlySequence<T>(Array.Empty<T>()) :
new ReadOnlySequence<T>(_head!, firstBufferStartIndex, _tail, lastBufferEndIndex);
private sealed class MemorySegment : ReadOnlySequenceSegment<T>
{
public MemorySegment(ReadOnlyMemory<T> memory, long runningIndex)
{
Memory = memory;
RunningIndex = runningIndex;
}
public MemorySegment Append(ReadOnlyMemory<T> nextMemory)
{
var next = new MemorySegment(nextMemory, RunningIndex + Memory.Length);
Next = next;
return next;
}
}
}
and this type for building an owned memory sequence:
public sealed class OwnedMemorySequence<T> : IDisposable
{
private readonly CollectionDisposable _disposable = new();
private readonly MemorySequence<T> _sequence = new();
public OwnedMemorySequence<T> Append(IMemoryOwner<T> memoryOwner)
{
_disposable.Add(memoryOwner);
_sequence.Append(memoryOwner.Memory);
return this;
}
public ReadOnlySequence<T> ReadOnlySequence => _sequence.ReadOnlySequence;
public ReadOnlySequence<T> CreateReadOnlySequence(int firstBufferStartIndex, int lastBufferEndIndex) =>
_sequence.CreateReadOnlySequence(firstBufferStartIndex, lastBufferEndIndex);
public void Dispose() => _disposable.Dispose();
}
which depends on owned memory span extension methods I stole from here:
public static class MemoryOwnerSliceExtensions
{
public static IMemoryOwner<T> Slice<T>(this IMemoryOwner<T> owner, int start, int length)
{
if (start == 0 && length == owner.Memory.Length)
return owner;
return new SliceOwner<T>(owner, start, length);
}
public static IMemoryOwner<T> Slice<T>(this IMemoryOwner<T> owner, int start)
{
if (start == 0)
return owner;
return new SliceOwner<T>(owner, start);
}
private sealed class SliceOwner<T> : IMemoryOwner<T>
{
private readonly IMemoryOwner<T> _owner;
public Memory<T> Memory { get; }
public SliceOwner(IMemoryOwner<T> owner, int start, int length)
{
_owner = owner;
Memory = _owner.Memory.Slice(start, length);
}
public SliceOwner(IMemoryOwner<T> owner, int start)
{
_owner = owner;
Memory = _owner.Memory[start..];
}
public void Dispose() => _owner.Dispose();
}
}
This code is all completely untested; use at your own risk.
Related
This is a request-response model (like HTTP) but over sockets/websockets. We know which response corresponds to which request by comparing the request IDs.
The workflow is as following:
Subscribe to the observables Error, ContractDetails (_itemObservable) and ContractDetailsEnd (_itemEndObservable)
Match request id to response id and push the corresponding messages via .OnNext(...) to ContractDetails. When there is nothing else left to push, push a final message to ContractDetailsEnd which essentially does .OnCompleted.
Cleanup – unsubscribe, i.e. dispose the observables
In the first snippet below I use a List<TItemArgs>, CancellationTokenSource and a TaskCompletionSource<TItemArgs>. That's completely unnecessary if everything was Rx style. It would have definitely been less lines of code too.
The second snippet is my personal attempt to make it look more Rx like. It has some issues that I want to resolve:
I don't think the try/catch block is necessary since .Subscribe could handle errors
.Timeout didn't work out for me – if the request is not matched within a few seconds, it should return a Result<IEnumerable<TItemArgs>>.FromError(new TimeoutError(...))
In addition to the errors, a message pushed to _errorSubject should also return an error such as Result<IEnumerable<TItemArgs>>.FromError(new RemoteError(...)) but .Merge(errorMessages.Any(_ => false)) is not working.
In my attempt I use ReplaySubject in opposed to AsyncSubject because I believe AsyncSubject is only useful when I'm only interested in the last value of the sequence and want to avoid getting all previous values, which is not in my case. In my case I want to return all values, so ReplaySubject would be more suitable as it keeps track of all the previous values and wants all subscribers to receive the same values, regardless of when they subscribed.
public async ValueTask<Result<IEnumerable<TItemArgs>>> ExecuteAsync(Action<int> action)
{
var requestId = _client.GetNextRequestId();
var data = new List<TItemArgs>();
var cts = new CancellationTokenSource(_timeout);
var tcs = new TaskCompletionSource<IEnumerable<TItemArgs>>();
cts.Token.Register(() =>
{
tcs.TrySetCanceled();
}, false);
void OnError(ErrorData msg)
{
tcs.SetException(new IBClientException(msg.RequestId, msg.Code, msg.Message, msg.AdvancedOrderRejectJson));
}
void OnDetails(TItemArgs item)
{
data.Add(item);
}
void OnDetailsEnd(TItemListEndArgs item)
{
tcs.TrySetResult(data);
}
var disposable = new CompositeDisposable();
_client.Error
.Where(e => HasRequestId && e.RequestId == requestId)
.Subscribe(OnError)
.DisposeWith(disposable);
_itemObservable
.Where(item => MatchRequest(item, _itemRequestIdExtractor, requestId))
.Subscribe(OnDetails)
.DisposeWith(disposable);
_itemEndObservable
.Where(item => MatchRequest(item, _itemListEndRequestIdExtractor, requestId))
.Subscribe(OnDetailsEnd)
.DisposeWith(disposable);
action(requestId);
try
{
await tcs.Task.ContinueWith(x =>
{
disposable.Dispose();
cts.Dispose();
}, TaskContinuationOptions.RunContinuationsAsynchronously);
return Result<IEnumerable<TItemArgs>>.FromSuccess(tcs.Task.Result);
}
catch (Exception e)
{
return Result<IEnumerable<TItemArgs>>.FromError(new RemoteError(e.Message, null));
}
}
My attempt
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
var client = new IBClient();
var result = await client.GetContractDetailsAsync();
if (result.Success)
{
foreach (var item in result.Data!)
{
Console.WriteLine($"RequestId: {item.RequestId} | Data: {item.ContractDetails}");
}
}
public sealed class IBClient
{
private int _nextValidId;
private readonly Subject<ErrorData> _errorSubject = new();
public IObservable<ErrorData> Error => _errorSubject.AsObservable();
private readonly Subject<ContractDetailsData> _contractDetailsSubject = new();
public IObservable<ContractDetailsData> ContractDetails => _contractDetailsSubject.AsObservable();
private readonly Subject<RequestEndData> _contractDetailsEndSubject = new();
public IObservable<RequestEndData> ContractDetailsEnd => _contractDetailsEndSubject.AsObservable();
public ValueTask<Result<IEnumerable<ContractDetailsData>>> GetContractDetailsAsync()
{
return new PendingRequest<ContractDetailsData, RequestEndData>(
this,
ContractDetails,
ContractDetailsEnd,
e => e.RequestId,
e => e.RequestId)
.ExecuteAsync(reqId => TestCall(reqId));
}
private void TestCall(int requestId)
{
_contractDetailsSubject.OnNext(new ContractDetailsData(requestId, "hey from test call"));
_contractDetailsSubject.OnNext(new ContractDetailsData(requestId, "hey two"));
// TODO: Errors doesn't seem to work
// _errorSubject.OnNext(new ErrorData(requestId, 123, "Error happened", ""));
_contractDetailsEndSubject.OnNext(new RequestEndData(requestId));
// There shouldn't be matched.
_contractDetailsSubject.OnNext(new ContractDetailsData(123, "fake ones, so we know it works"));
_contractDetailsEndSubject.OnNext(new RequestEndData(123));
}
public int GetNextRequestId()
{
return Interlocked.Increment(ref _nextValidId);
}
}
public sealed class PendingRequest<TItemArgs, TItemListEndArgs>
{
private readonly TimeSpan _timeout = TimeSpan.FromSeconds(2);
private readonly IBClient _client;
private readonly IObservable<TItemArgs> _itemObservable;
private readonly IObservable<TItemListEndArgs> _itemEndObservable;
private readonly Func<TItemArgs, int>? _itemRequestIdExtractor;
private readonly Func<TItemListEndArgs, int>? _itemListEndRequestIdExtractor;
public PendingRequest(
IBClient client,
IObservable<TItemArgs> itemObservable,
IObservable<TItemListEndArgs> itemEndObservable,
Func<TItemArgs, int>? itemRequestIdExtractor = null,
Func<TItemListEndArgs, int>? itemListEndRequestIdExtractor = null)
{
_client = client;
_itemObservable = itemObservable;
_itemEndObservable = itemEndObservable;
_itemRequestIdExtractor = itemRequestIdExtractor;
_itemListEndRequestIdExtractor = itemListEndRequestIdExtractor;
}
private bool HasRequestId => _itemRequestIdExtractor != null && _itemListEndRequestIdExtractor != null;
public async ValueTask<Result<IEnumerable<TItemArgs>>> ExecuteAsync(Action<int> action, IScheduler? scheduler = null)
{
scheduler ??= ImmediateScheduler.Instance;
var requestId = _client.GetNextRequestId();
var results = new ReplaySubject<TItemArgs>();
try
{
var errorMessages = _client.Error
.Where(e => HasRequestId && e.RequestId == requestId);
using (_itemObservable
.Where(item => MatchRequest(item, _itemRequestIdExtractor, requestId))
.ObserveOn(scheduler)
.Subscribe(results))
using (_itemEndObservable
.Any(item => MatchRequest(item, _itemListEndRequestIdExtractor, requestId))
.Merge(errorMessages.Any(_ => false)) // TODO: ???
.ObserveOn(scheduler)
.Subscribe(_ => results.OnCompleted()))
{
action(requestId);
// Don't want an Exception thrown if there result list is empty
await results.DefaultIfEmpty();
return Result<IEnumerable<TItemArgs>>.FromSuccess(results.ToEnumerable());
}
}
catch (Exception ex)
{
return Result<IEnumerable<TItemArgs>>.FromError(new RemoteError(ex.Message, null));
}
}
private bool MatchRequest<T>(T item, Func<T, int>? idExtractor, int id)
{
return !HasRequestId || (idExtractor != null && idExtractor(item) == id);
}
}
public sealed class ContractDetailsData
{
public ContractDetailsData(int requestId, string contractDetails)
{
RequestId = requestId;
ContractDetails = contractDetails;
}
public int RequestId { get; }
public string ContractDetails { get; }
}
public sealed class ErrorData
{
public ErrorData(int requestId, int code, string message, string advancedOrderRejectJson)
{
RequestId = requestId;
Code = code;
Message = message;
AdvancedOrderRejectJson = advancedOrderRejectJson;
}
public int RequestId { get; }
public int Code { get; }
public string Message { get; }
public string AdvancedOrderRejectJson { get; }
}
public sealed class RequestEndData
{
public RequestEndData(int requestId)
{
RequestId = requestId;
}
public int RequestId { get; }
}
public class IBClientException : Exception
{
public IBClientException(int requestId, int errorCode, string message, string advancedOrderRejectJson)
: base(message)
{
RequestId = requestId;
ErrorCode = errorCode;
AdvancedOrderRejectJson = advancedOrderRejectJson;
}
public IBClientException(string err)
: base(err)
{
}
public IBClientException(Exception e)
{
Exception = e;
}
public int RequestId { get; }
public int ErrorCode { get; }
public string? AdvancedOrderRejectJson { get; }
public Exception? Exception { get; }
}
public abstract record Error(int? Code, string Message, object? Data);
public record RemoteError : Error
{
public RemoteError(string message, object? data) : base(null, message, data)
{
}
public RemoteError(int? code, string message, object? data) : base(code, message, data)
{
}
}
public record Result<T>(bool Success, T? Data, Error? Error)
{
public Result(T data) : this(true, data, default)
{
}
public Result(Error error) : this(false, default, error)
{
}
public static Result<T> FromSuccess(T data)
{
return new Result<T>(data);
}
public static Result<T> FromError<TError>(TError error) where TError : Error
{
return new Result<T>(error);
}
}
public static class DisposableExtensions
{
public static T DisposeWith<T>(this T disposable, ICollection<IDisposable> collection)
where T : IDisposable
{
ArgumentNullException.ThrowIfNull(disposable);
ArgumentNullException.ThrowIfNull(collection);
collection.Add(disposable);
return disposable;
}
}
Here's a good start:
IObservable<TItemArgs> observable =
Observable
.Merge(
_itemObservable
.Where(item => MatchRequest(item, _itemRequestIdExtractor, requestId))
.Select(item => Notification.CreateOnNext(item))
.Take(1),
_itemEndObservable
.Any(item => MatchRequest(item, _itemListEndRequestIdExtractor, requestId))
.Select(item => Notification.CreateOnCompleted<TItemArgs>()))
.Dematerialize();
And here's a final observable without the ReplaySubject.
private static async Task<Result<IEnumerable<Details>>> Test(IScheduler scheduler) =>
await
Observable
.Defer(() =>
{
var client = new IBClient();
var requestId = 1;
return Observable.Create<Result<IEnumerable<Details>>>(o =>
{
IDisposable subscription =
Observable
.Merge(
client.Details.Where(x => x.RequestId == requestId).Select(x => Notification.CreateOnNext(x)),
client.DetailsEnd.Any(x => x.RequestId == requestId).Select(x => Notification.CreateOnCompleted<Details>()),
client.Error.Where(x => x.RequestId == requestId).Select(x => Notification.CreateOnError<Details>(new Exception($"Code: {x.Code}, Message: {x.Message}"))))
.Dematerialize()
.Synchronize()
.ToArray()
.Select(items => Result<IEnumerable<Details>>.FromSuccess(items))
.Catch<Result<IEnumerable<Details>>, Exception>(ex => Observable.Return(Result<IEnumerable<Details>>.FromError(new Error(null, ex.Message, null))))
.Timeout(TimeSpan.FromSeconds(2))
.ObserveOn(scheduler)
.Subscribe(o);
client.Emit(2, 4);
client.Emit(requestId, 42);
client.EmitEnd(2);
client.Emit(requestId, 62);
client.Emit(requestId, 123);
//client.EmitError(requestId, 123, "Something bad happened");
client.EmitEnd(requestId);
client.Emit(requestId, 1);
return subscription;
});
});
My use case:
In a single threaded application, I need to serialize arbitrary classes for logging purposes.
The arbitrary classes are predominantly translated in an automated way from a massive VB6 application into .NET.
If serialized without a timeout, the serialization method will loop until it runs out of memory.
This is what I have currently:
internal class Serializer
{
private readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public volatile string result = null;
public volatile Func<string> toExecute = null;
public Thread thread;
public ManualResetEventSlim messageToSender = new ManualResetEventSlim(false);
public ManualResetEventSlim messageToReceiver = new ManualResetEventSlim(false);
public Serializer()
{
thread = new Thread(new ThreadStart(run));
thread.Start();
}
~Serializer()
{
try
{
if (messageToSender != null) messageToSender.Dispose();
}
catch { };
try
{
if (messageToReceiver != null) messageToReceiver.Dispose();
}
catch { };
}
public volatile bool ending = false;
public void run()
{
while (!ending)
{
try
{
if (toExecute != null)
{
result = toExecute();
}
messageToReceiver.Reset();
messageToSender.Set();
messageToReceiver.Wait();
}
catch (ThreadInterruptedException)
{
log.Warn("Serialization interrupted");
break;
}
catch (ThreadAbortException)
{
Thread.ResetAbort();
result = null;
}
catch (Exception ex)
{
log.Error("Error in Serialization", ex);
Console.WriteLine(ex);
break;
}
}
}
}
public class LocalStructuredLogging
{
private static volatile Serializer _serializer;
private static Serializer serializer
{
get
{
if (_serializer == null)
{
_serializer = new Serializer();
}
return _serializer;
}
}
public void LogStucturedEnd()
{
try
{
if (serializer != null)
{
serializer.ending = true;
serializer.thread.Interrupt();
}
}
catch { }
}
internal ConcurrentDictionary<long, bool> disallowedToSerialize = new ConcurrentDictionary<long, bool>();
public string TrySerialize<T>(T payload, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
bool dummy;
unchecked
{
if (disallowedToSerialize.TryGetValue(hashEl, out dummy))
{
return "°,°";
}
}
serializer.toExecute = () =>
{
try
{
return Newtonsoft.Json.JsonConvert.SerializeObject(payload, new Newtonsoft.Json.JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore });
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°°°";
}
};
try
{
serializer.messageToSender.Reset();
serializer.messageToReceiver.Set();
if (serializer.messageToSender.Wait(6000))
{
return Interlocked.Exchange(ref serializer.result, null);
}
serializer.toExecute = null;
serializer.thread.Abort();
serializer.messageToSender.Wait(2000);
disallowedToSerialize.TryAdd(hashEl, false);
return "°§°";
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°-°";
}
}
}
The code is called as in the following (test is an arbitrary class instance):
var logger = new LocalStructuredLogging();
var rr5 = logger.TrySerialize(test);
Although it seems to do the job, there are some issues with it:
it has a dependency on Thread.Abort
it is time dependent, so it will thus produce varied results on a loaded system
every class instance is treated like every other class instance - no tweaking
...
So, are there any better solutions available ?
Based upon dbc's excellent answer, I managed to create a better timed serializer.
It resolves all 3 issues mentioned above:
public class TimedJsonTextWriter : JsonTextWriter
{
public int? MaxDepth { get; set; }
public TimeSpan? MaxTimeUsed { get; set; }
public int MaxObservedDepth { get; private set; }
private DateTime start = DateTime.Now;
public TimedJsonTextWriter(TextWriter writer, JsonSerializerSettings settings, TimeSpan? maxTimeUsed)
: base(writer)
{
this.MaxDepth = (settings == null ? null : settings.MaxDepth);
this.MaxObservedDepth = 0;
this.MaxTimeUsed = maxTimeUsed;
}
public TimedJsonTextWriter(TextWriter writer, TimeSpan? maxTimeUsed, int? maxDepth = null)
: base(writer)
{
this.MaxDepth = maxDepth;
this.MaxTimeUsed = maxTimeUsed;
}
public override void WriteStartArray()
{
base.WriteStartArray();
CheckDepth();
}
public override void WriteStartConstructor(string name)
{
base.WriteStartConstructor(name);
CheckDepth();
}
public override void WriteStartObject()
{
base.WriteStartObject();
CheckDepth();
}
uint checkDepthCounter = 0;
private void CheckDepth()
{
MaxObservedDepth = Math.Max(MaxObservedDepth, Top);
if (Top > MaxDepth)
throw new JsonSerializationException($"Depth {Top} Exceeds MaxDepth {MaxDepth} at path \"{Path}\"");
unchecked
{
if ((++checkDepthCounter & 0x3ff) == 0 && DateTime.Now - start > MaxTimeUsed)
throw new JsonSerializationException($"Time Usage Exceeded at path \"{Path}\"");
}
}
}
public class LocalStructuredLogging
{
public void LogStucturedEnd()
{
}
internal HashSet<long> disallowedToSerialize = new HashSet<long>();
public string TrySerialize<T>(T payload, int maxDepth = 100, int secondsToTimeout = 2, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
if (disallowedToSerialize.Contains(hashEl))
{
return "°,°";
}
try
{
var settings = new JsonSerializerSettings { MaxDepth = maxDepth, ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore };
using (var writer = new StringWriter())
{
using (var jsonWriter = new TimedJsonTextWriter(writer, settings, new TimeSpan(0, 0, secondsToTimeout)))
{
JsonSerializer.Create(settings).Serialize(jsonWriter, payload);
// Log the MaxObservedDepth here, if you want to.
}
return writer.ToString();
}
}
catch (Exception)
{
disallowedToSerialize.Add(hashEl);
return "°-°";
}
}
}
The only issue remaining are the Hash collisions, which are easy to solve (e.g. by using the source file name as well or use another type of Collection).
The correct way to run an action timed would be to do something like the following. I would recommend taking a second look at how serialization should work as well :).
/// <summary>
/// Run an action timed.
/// </summary>
/// <param name="action">Action to execute timed.</param>
/// <param name="secondsTimout">Seconds before Task should cancel.</param>
/// <returns></returns>
public static async Task RunTimeout(Action action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
await Task.Run(action, tokenSource.Token);
}
You may also want to return a variable upon the completion of your timed task. That can be done like so...
public static async Task<T> RunTimeout<T>(Func<T> action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
var result = await Task.Run(action, tokenSource.Token);
return result;
}
I'm trying to await SubscribeToKlineUpdatesAsync, which is being casted from IExchangeClient to BinanceSpotClient, but it doesn't let me do it. The following error message appears:
The name 'await' does not exist in the current context
What is the correct syntax to do that?
public class BinanceSpotClient : IExchangeClient
{
...
public async Task<CallResult<UpdateSubscription>> SubscribeToKlineUpdatesAsync(string symbol, TimeFrame timeFrame, Action<IBinanceStreamKlineData> callback)
{
return await _socketClient.Spot.SubscribeToKlineUpdatesAsync(symbol, timeFrame.ToKlineInterval(), callback);
}
}
public class LiveTradeManager : ITradeManager
{
private static readonly ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private readonly IExchangeClient _exchangeClient;
private readonly TradeOptions _tradeOptions;
public LiveTradeManager(IExchangeClient exchangeClient, TradeOptions tradeOptions)
{
_exchangeClient = exchangeClient;
_tradeOptions = tradeOptions;
}
public void Run()
{
try
{
var strategy = StrategyUtils.GetStrategyByName("RsiStrategy");
var result = await (_exchangeClient as BinanceSpotClient).SubscribeToKlineUpdatesAsync(_tradeOptions.Pair, strategy.TimeFrame, data => // the error appears here
{
});
if (result.Success)
{
result.Data.ConnectionLost += () =>
{
_logger.Error("Connection lost.");
};
result.Data.ConnectionRestored += (e) =>
{
_logger.Info("Connection was restored.");
};
}
}
catch (Exception ex)
{
_logger.Error($"{ex.Message} | Stack trace: {ex.StackTrace}");
}
}
}
Change the signature of the Run method to be async and return Task (most likely change of ITradeManager also required):
public async Task Run()
{
// you can use await here
}
I am creating an application in Xamarin.Forms - it is an event management application - in the application the user can sign up to events and create events. When you create an event - you are given a code for that event. There is a window in which you can input the code and then it registers you to that event. So for example if someone wants to check who is coming to a birthday party they could put the code on the invitation and the people who receive the invitation could open the app - input the code - and say whether they are going or not and add some messages.
I followed this tutorial to implement SQLite into Xamarin.Forms:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
Here is the code:
public class LocalEventDatabase : ILocalEventDatabase
{
static readonly Lazy<SQLiteAsyncConnection> lazyInitializer = new Lazy<SQLiteAsyncConnection>(() =>
{
return new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
});
static SQLiteAsyncConnection Database => lazyInitializer.Value;
static bool initialized = false;
public LocalEventDatabase()
{
InitializeAsync().SafeFireAndForget(false, new Action<Exception>(async(Exception ex) =>
{
await Acr.UserDialogs.UserDialogs.Instance.AlertAsync(string.Format($"Don't panic! An exception has occurred: {ex.Message}"), "An exception has occurred", "OK");
}));
}
async Task InitializeAsync()
{
if (!initialized)
{
if (!Database.TableMappings.Any(m => m.MappedType.Name == typeof(Event).Name))
{
await Database.CreateTablesAsync(CreateFlags.None, typeof(Event)).ConfigureAwait(false); // not entirely understood what configure await is?
}
initialized = true;
}
}
public async Task<List<Event>> GetRegisteredEventsAsync()
{
return await Database.Table<Event>().ToListAsync();
}
public async Task<Event> GetEventAsync(Guid id)
{
return await Database.Table<Event>().Where(i => i.EventGuid == id).FirstOrDefaultAsync();
}
public async Task<int> InsertEventAsync(Event #event)
{
return await Database.InsertAsync(#event);
}
public async Task<int> DeleteEventAsync(Event #event)
{
return await Database.DeleteAsync(#event);
}
}
Here is the code behind for searching through the codes:
public class FindEventViewModel : ViewModelBase
{
private ObservableCollection<Event> _results;
private ObservableCollection<Event> _events;
public ICommand SearchCommand => new Command(OnSearchCommand);
public string EntryText { get; set; }
// FAB stands = 'floating action button'
public ObservableCollection<Event> Results
{
get => _results;
set
{
_results = value;
RaisePropertyChanged(nameof(Results));
}
}
public ObservableCollection<Event> Events
{
get => _events;
set
{
_events = value;
RaisePropertyChanged(nameof(Events));
}
}
public FindEventViewModel(IDialogService dialogService,
IEventService eventService,
IShellNavigationService shellNavigationService,
IThemeService themeService,
ILocalEventDatabase localEventDatabase) : base(dialogService, eventService, shellNavigationService, themeService, localEventDatabase)
{
}
public async Task<FindEventViewModel> OnAppearing()
{
await LoadData();
return this;
}
private async Task LoadData()
{
var data = await _eventService.GetAllEventsAsync();
Events = new ObservableCollection<Event>(data);
Results = new ObservableCollection<Event>();
}
public async void OnSearchCommand()
{
if (SearchEvents(EntryText).SearchResult == SearchResult.Match)
{
_dialogService.ShowAdvancedToast(string.Format($"Found matching event"), Color.Green, Color.White, TimeSpan.FromSeconds(2));
var eventFound = SearchEvents(EntryText).EventFound;
Results.Add(eventFound);
RaisePropertyChanged(nameof(eventFound));
// may be unneccessary to use it directly rather than with DI but in this case it is needed in order to evaluate what the user has pressed
var result = await UserDialogs.Instance.ConfirmAsync(new Acr.UserDialogs.ConfirmConfig()
{
Title = "Sign up?",
Message = string.Format($"Would you like to sign up to {eventFound.EventTitle}?"),
OkText = "Yes",
CancelText = "No",
});
if (result)
{
await _localEventDatabase.InsertEventAsync(eventFound);
_dialogService.ShowAdvancedToast(string.Format($"You signed up to {eventFound.EventTitle} successfully"), Color.CornflowerBlue, Color.White, TimeSpan.FromSeconds(2));
MessagingCenter.Send(eventFound as Event, MessagingCenterMessages.EVENT_SIGNEDUP);
}
}
else
{
_dialogService.ShowAdvancedToast(string.Format($"No event found matching query - retry?"), Color.Red, Color.White, TimeSpan.FromSeconds(2));
}
}
public EventSearchResult SearchEvents(string code)
{
foreach (Event _event in Events)
{
if (_event.EventCode == code)
{
return new EventSearchResult()
{
SearchResult = SearchResult.Match,
EventFound = _event,
};
}
}
return new EventSearchResult()
{
SearchResult = SearchResult.NoMatch,
};
}
}
The method returns a search result and the event found. One problem - the user can sign up to the same event twice. In the tutorial I followed it doesn't mention how to check for any duplicate items in the database table?
I'm implementing the MailChimp.NET wrapper in both synchronous and asynchronous ways and calls are going through without a problem, BUT results tend to get lost in the synchronous methods. In other words, if I send 100 members to be added (by batches of 10 due to the simultaneous connections limit of the MailChimp API), all 100 will indeed be visible in my MC audience but I'll loose from 5 to 25% of the results on code side. Here's the concerned bit of my implementation :
public class MailChimpClient : IDisposable
{
private MailChimpManager _mcm;
private string _apiKey;
private bool _isDisposed;
private ConcurrentQueue<MailChimpMember> _updatedMembersQueue;
private ConcurrentQueue<MailChimpBaseException> _exceptionsQueue;
private const int BatchSize = 10;
private const int TaskDelay = 100;
private ConcurrentQueue<MailChimpMember> UpdatedMembersQueue
{
get { return _updatedMembersQueue = _updatedMembersQueue ?? new ConcurrentQueue<MailChimpMember>(); }
set { _updatedMembersQueue = value; }
}
private ConcurrentQueue<MailChimpBaseException> ExceptionsQueue
{
get { return _exceptionsQueue = _exceptionsQueue ?? new ConcurrentQueue<MailChimpBaseException>(); }
set { _exceptionsQueue = value; }
}
public MailChimpClient(string apiKey)
{
_apiKey = apiKey;
_mcm = new MailChimpManager(apiKey);
}
private async Task AddOrUpdateMember(MailChimpMember member, string listId)
{
try
{
var model = member.ToApiMember();
model = await _mcm.Members.AddOrUpdateAsync(listId, model);
UpdatedMembersQueue.Enqueue(new MailChimpMember(model));
await Task.Delay(TaskDelay);
}
catch (Exception ex)
{
var mccex = new MailChimpClientException($"Error adding/updating member \"{(member != null ? member.MailAddress.ToString() : "NULL")}\" to list with ID \"{listId}\".", ex);
ExceptionsQueue.Enqueue(mccex);
}
}
private MailChimpClientResult AddOrUpdateMemberRange(IEnumerable<MailChimpMember> members, string listId)
{
var batches = members.GetBatches(BatchSize);
var result = new MailChimpClientResult();
var i = 0;
foreach (var batch in batches)
{
AddOrUpdateMemberBatch(batch, listId);
i++;
FlushQueues(ref result);
}
return result;
}
private void AddOrUpdateMemberBatch(MailChimpMember[] batch, string listId)
{
Task.WaitAll(batch.Select(async b => await AddOrUpdateMember(b, listId)).ToArray(), -1);
}
private void FlushQueues(ref MailChimpClientResult result)
{
result.UpdatedMembers.FlushQueue(UpdatedMembersQueue);
result.Exceptions.FlushQueue(ExceptionsQueue);
}
public MailChimpClientResult AddOrUpdate(MailChimpMember member, string listId)
{
return AddOrUpdateMemberRange(new MailChimpMember[] { member }, listId);
}
public MailChimpClientResult AddOrUpdate(IEnumerable<MailChimpMember> members, string listId)
{
return AddOrUpdateMemberRange(members, listId);
}
}
public static class CollectionExtensions
{
public static T[][] GetBatches<T>(this IEnumerable<T> items, int batchSize)
{
var result = new List<T[]>();
var batch = new List<T>();
foreach (var t in items)
{
if (batch.Count == batchSize)
{
result.Add(batch.ToArray());
batch.Clear();
}
batch.Add(t);
}
result.Add(batch.ToArray());
batch.Clear();
return result.ToArray();
}
public static void FlushQueue<T>(this IList<T> list, ConcurrentQueue<T> queue)
{
T item;
while (queue.TryDequeue(out item))
list.Add(item);
}
}
MailChimpMember being a public copy of the MailChimp.NET Member. The problem seems to happen in the batch processing method, the Task.WaitAll(...) instruction firing its completion event before all calls are complete, therefore not all results are queued. I tried delaying the execution of each individual treatment with Task.Delay() but with little to no result.
Does anyone have an idea what is failing in my implementation ?