In my Prism module in ViewModel class in OnNavigatedTo method
I would like to fill an ObservableCollection with results of multiple async calls without waiting for all calls to complete.
I am using answer from this question:
How to hydrate a Dictionary with the results of async calls?
The following code is a cleaned-up version of my real code:
My status class:
public class Status
{
public string ipAddress;
public string status;
}
My view model:
using Prism.Mvvm;
using Prism.Regions;
public class StatusViewModel : BindableBase, INavigationAware
{
ObservableCollection<Status> statusCollection = new ObservableCollection<Status>();
HttpClient httpClient = new HttpClient();
Ping ping = new Ping();
List<string> ipAddressList = new List<string>();
public void OnNavigatedTo(NavigationContext navigationContext)
{
GetEveryStatus();
}
public void GetEveryIp() // this is not important, works ok
{
var addressBytes = Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).GetAddressBytes();
for (byte i = 1; i < 255; ++i)
{
addressBytes[3] = i;
string ipAddress = new IPAddress(addressBytes).ToString();
if (ping.Send(ipAddress, 10).Status == IPStatus.Success)
{
ipAddressList.Add(ipAddress);
}
}
}
public void GetEveryStatus() // this is important, here is the problem
{
GetEveryIp();
var task = GetStatusArray(ipAddressList);
statusCollection.AddRange(task.Result);
}
// solution from stackoverflow, but it throws exception
public async Task<Status[]> GetStatusArray(List<string> ipAddressList)
{
Status[] statusArray = await Task.WhenAll(
ipAddressList.Select(
async ipAddress => new Status(
ipAddress,
await httpClient.GetStringAsync("http://" + ipAddress + ":8080" + "/status")
)
)
);
return statusArray;
}
}
but that didn't work because GetStringAsync can throw an exception, so I changed it to this:
public void GetEveryStatus() // this is important, here is the problem
{
GetEveryIp();
foreach (string ipAddress in ipAddressList)
{
try
{
var task = httpClient.GetStringAsync("http://" + ipAddress + ":8080" + "/status");
statusCollection.Add(new Status(ipAddress, task.Result));
}
catch (Exception)
{
}
}
}
but it still doesn't work.
What is the right way to do this? Thank you!
Thanks to #AccessDenied for explaining the role of async in interface implementation.
Thanks to #Selvin for explaining Task.Result and Task.Wait.
If anyone is interesed in the final solution, here it is:
PositioningModule is a hardware device, this class has nothing to do with Prism.Modularity.IModule
public class PositioningModule
{
public string IpAddress { get; set; }
public PositioningModuleStatus PositioningModuleStatus { get; set; }
public PositioningModule(string ipAddress, PositioningModuleStatus positioningModuleStatus)
{
IpAddress = ipAddress;
PositioningModuleStatus = positioningModuleStatus;
}
}
The ViewModel:
I had to use BindingOperations.EnableCollectionSynchronization and lock on the ObservableCollection. This is the main reason why it didn't work async before!
Changing OnNavigatedTo to async blocked the UI, so I used Task.Run().
using Prism.Mvvm;
using Prism.Regions;
public class DomePositioningViewModel : BindableBase, INavigationAware
{
ObservableCollection<PositioningModule> _positioningModuleCollection = new ObservableCollection<PositioningModule>();
readonly object _lock = new object();
DomePositioningModel _domePositioningModel = new DomePositioningModel();
public DomePositioningViewModel()
{
BindingOperations.EnableCollectionSynchronization(_positioningModuleCollection, _lock);
}
public /* async */ void OnNavigatedTo(NavigationContext navigationContext)
{
//await _domePositioningModel.ScanForModulesAsync(AddModule); - this blocks the UI
Task.Run(() => _domePositioningModel.ScanForModulesAsync(AddModule));
}
private void AddModule(PositioningModule module)
{
lock (_lock)
{
_positioningModuleCollection.Add(module);
}
}
}
The Model:
I changed Send to SendPingAsync and I had to use new Ping() instead of ping.
Using Select instead of foreach to make the calls parallel made everything much faster!
#define PARALLEL
public class DomePositioningModel
{
private readonly HttpClient _httpClient = new HttpClient();
public DomePositioningModel()
{
_httpClient.Timeout = TimeSpan.FromMilliseconds(50);
}
public async Task ScanForModulesAsync(Action<PositioningModule> AddModule)
{
List<string> ipAddressList = new List<string>();
var addressBytes = Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).GetAddressBytes();
for (addressBytes[3] = 1; addressBytes[3] < 255; ++addressBytes[3])
{
ipAddressList.Add(new IPAddress(addressBytes).ToString());
}
//Ping ping = new Ping(); - this behaves strangely, use "new Ping()" instead of "ping"
#if PARALLEL
var tasks = ipAddressList.Select(async ipAddress => // much faster
#else
foreach (string ipAddress in ipAddressList) // much slower
#endif
{
PingReply pingReply = await new Ping().SendPingAsync(ipAddress, 10); // use "new Ping()" instead of "ping"
if (pingReply.Status == IPStatus.Success)
{
try
{
string status = await _httpClient.GetStringAsync("http://" + ipAddress + ":8080" + "/status");
if (Enum.TryParse(status, true, out PositioningModuleStatus positioningModuleStatus))
{
AddModule?.Invoke(new PositioningModule(ipAddress, positioningModuleStatus));
}
}
catch (TaskCanceledException) // timeout
{
}
catch (HttpRequestException) // could not reach IP
{
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
}
}
#if PARALLEL
);
await Task.WhenAll(tasks);
#endif
}
}
It didn't benchmark it because the difference is so obvious - about 0.5 sec instead of 14 sec!
Related
I wrote two views in my application, and when I am changin from "Home" to "Details" it causes lag for about 0.5 - 1 sec. I understand, that the reason are my API calls in the constructor, but I added threading, and first filling in an empty properties, and after threads finish it should raise a OnPropertyChange. My "Details" viewmodel is here
public class DetailsViewModel : ObservableObject
{
private ObservableCollection<Market> _markets;
private Asset _asset;
private readonly Uri _baseUri = new Uri("https://cryptingup.com/api/assets/");
public RestClient Client { get; set; }
public ObservableCollection<Market> Markets
{
get
{
return _markets;
}
set
{
_markets = value;
OnPropertyChanged();
}
}
public Asset Asset
{
get
{
return _asset;
}
set
{
_asset = value;
OnPropertyChanged();
}
}
public DetailsViewModel(string id)
{
Client = new RestClient();
Markets = new ObservableCollection<Market>();
new Thread(() =>
{
RequestCurrencyDetails(id);
})
{
IsBackground = true
}.Start();
RequestAllMarkets(id);
new Thread(() =>
{
RequestAllMarkets(id);
})
{
IsBackground = true
}.Start();
}
private void RequestCurrencyDetails(string id)
{
Uri uri = new Uri(_baseUri, id);
var request = new RestRequest(uri.ToString());
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json");
var response = Client.GetAsync(request).GetAwaiter().GetResult();
var curemodel = JsonConvert.DeserializeObject<CurrencyModelCU>(response.Content);
Asset = curemodel.Asset;
}
private void RequestAllMarkets(string id)
{
string marketPath = id + "/markets";
Uri uri = new Uri(_baseUri, marketPath);
var request = new RestRequest(uri.ToString());
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json");
var response = Client.GetAsync(request).GetAwaiter().GetResult();
var marmodel = JsonConvert.DeserializeObject<MarketsModel>(response.Content);
ObservableCollection<Market> temp = new ObservableCollection<Market>();
for (int i = 0; i < 10; i++)
{
temp.Add(marmodel.Markets[i]);
}
Markets = temp;
}
}
I know, that I did something wrong with threading here, but I can't get the reason by myself. Thanks for answer
I highly recommend using await async instead of Thread. Also RequestAllMarkets(id); is called synchronously. this is most likley causing the "lag".
Here is a sample how to use Task.Run to "fire and forget the loading operation.
https://dotnetfiddle.net/sK3ivq
using System;
using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
Console.WriteLine("start");
Stopwatch sw = new Stopwatch();
sw.Start();
DetailsViewModel vm = new DetailsViewModel();
Console.WriteLine($"already back from ctor -> no lag. prove: {sw.ElapsedMilliseconds}ms foo:{vm.Foo}");
// we use thread sleep -> in wpf app you don't have to wait because property change took place in VM.
Thread.Sleep(1000);
Console.WriteLine($"we waited long enough...: {sw.ElapsedMilliseconds}ms foo:{vm.Foo}");
}
public class DetailsViewModel
{
public DetailsViewModel()
{
Foo= "not initialized";
Task.Run(()=> Init());
}
public async Task Init()
{
await Task.Delay(500);
Foo = "bar";
}
public string Foo {get;set;}
}
}
If I call Stop(), OperationCanceledException is happened and _writer.TryComplete(exp) is true. But _reader.Completion Task is still not completed.
Is it desired behavior of Channels? If yes can someone tell me how to stop a Channel without waiting till it's empty and have its Completion Task in Completed state?
public interface IItem
{
Uri SourceUri { get; }
string TargetPath { get; }
}
public class Item : IItem
{
public Item(Uri sourceUri, string targetPath)
{
SourceUri = sourceUri;
TargetPath = targetPath;
}
public Uri SourceUri { get; }
public string TargetPath { get; }
}
public class TestService
{
private readonly ChannelWriter<IItem> _writer;
private readonly ChannelReader<IItem> _reader;
private readonly CancellationTokenSource _cts;
public TestService()
{
_cts = new CancellationTokenSource();
Channel<IItem> channel = Channel.CreateUnbounded<IItem>();
_reader = channel.Reader;
_writer = channel.Writer;
}
public async Task QueueDownload(IItem information)
{
await _writer.WriteAsync(information);
}
public void StartDownload()
{
Task.Factory.StartNew(async () =>
{
await ProcessDownloadAsync();
}, TaskCreationOptions.LongRunning);
}
public void Stop()
{
_cts.Cancel();
//_writer.Complete();
//_writer = null;
Console.WriteLine("Stop");
}
public async Task Wait()
{
await _reader.Completion;
}
private async Task ProcessDownloadAsync()
{
try
{
while (await _reader.WaitToReadAsync(_cts.Token))
{
IItem information = await _reader.ReadAsync(_cts.Token);
using (WebClient webClient = new WebClient())
{
Console.WriteLine(information.TargetPath);
await webClient.DownloadFileTaskAsync(information.SourceUri,
information.TargetPath);
}
}
}
catch (OperationCanceledException exp)
{
bool res = _writer.TryComplete(exp);
}
}
}
static class Program
{
static async Task Main(string[] args)
{
TestService tSvc = new TestService();
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
tSvc.StartDownload();
Task t = tSvc.Wait();
tSvc.Stop();
await t;
Console.WriteLine("Finished");
}
}
The ChannelWriter.Complete method behaves a bit differently than one would expect. It is not invalidating instantly the contents of the channel. Instead, it just prevents adding more items in the channel. The existing items are still valid for consumption, and the ChannelReader.Completion property will not complete before all stored items are consumed.
The example below demonstrates this behavior:
var channel = Channel.CreateUnbounded<int>();
channel.Writer.TryWrite(1);
channel.Writer.Complete(new FileNotFoundException());
//channel.Reader.TryRead(out var data);
var completed = channel.Reader.Completion.Wait(500);
Console.WriteLine($"Completion: {(completed ? "OK" : "Timed-out")}");
Output:
Completion: Timed-out
You can uncomment the channel.Reader.TryRead line, to see the FileNotFoundException to emerge.
I am working with Azure Event Hub producer client and reading messages off of a kafka stream then pass it along to deserialize/map, then pass to Event Hub. I have the consume loop which is creating a task for each consume and then two methods to do processing(this seems to have greatly improved the speed from the kafka lag perspective. However, Event hub makes you create an event batch which I don't necessarily want to use. I just want to send the data one message at a time for now. In order to create a new batch I have to call Dispose(). I am running into an issue where there's another call to the function by the the time I call Dispose() and I get an error saying the object is being used by event hub.
I've also tried using the overload for eventHubProducerClient.SendAsync that allows you to pass in a IEnumerable but i'm running into the same issue with that.
So I believe this to be a synchronization issue, or maybe I need to do a lock somewhere?
Any help would be appreciated.
public void Execute()
{
using (_consumer)
{
try
{
_consumer.Subscribe(_streamConsumerSettings.Topic);
while (true)
{
var result = _consumer.Consume(1000);
if (result == null)
{
continue;
}
var process = Task.Factory.StartNew(() => ProcessMessage(result?.Message?.Value));
var send = process.ContinueWith(t => SendMessage(process.Result));
}
}
catch (ConsumeException e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
_cancelConsume = true;
_consumer.Close();
RestartConsumer();
}
}
}
public static EquipmentJson ProcessMessage(byte[] result)
{
var json = _messageProcessor.DeserializeAndMap(result);
return json;
}
public static void SendMessage(EquipmentJson message)
{
try
{
_eventHubClient.AddToBatch(message);
}
catch (Exception e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
}
}
public async Task AddToBatch(EquipmentJson message)
{
if
(!string.IsNullOrEmpty(message.EquipmentLocation))
{
try
{
var batch = await _equipmentLocClient.CreateBatchAsync();
batch.TryAdd(new EventData(Encoding.UTF8.GetBytes(message.EquipmentLocation)));
await _eventHubProducerClient.SendAsync(batch);
batch.Dispose();
_logger.LogInformation($"Data sent {DateTimeOffset.UtcNow}");
}
catch (Exception e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
}
}
}
public class EventHubClient : IEventHubClient
{
private readonly ILoggerAdapter<EventHubClient> _logger;
private readonly EventHubClientSettings _eventHubClientSettings;
private IMapper _mapper;
private static EventHubProducerClient _equipmentLocClient;
public EventHubClient(ILoggerAdapter<EventHubClient> logger, EventHubClientSettings eventHubClientSettings, IMapper mapper)
{
_logger = logger;
_eventHubClientSettings = eventHubClientSettings;
_mapper = mapper;
_equipmentLocClient = new EventHubProducerClient(_eventHubClientSettings.ConnectionString, _eventHubClientSettings.EquipmentLocation);
}
}
}
Based on my speculation in comments, I'm curious if refactoring to use async/await rather than the explicit continuation in the main loop may help. Perhaps something similar to the following LinqPad snippet:
async Task Main()
{
while (true)
{
var message = await Task.Factory.StartNew(() => GetText());
var events = new[] { new EventData(Encoding.UTF8.GetBytes(message)) };
await Send(events).ConfigureAwait(false);
}
}
public EventHubProducerClient client = new EventHubProducerClient("<< CONNECTION STRING >>");
public async Task Send(EventData[] events)
{
try
{
await client.SendAsync(events).ConfigureAwait(false);
"Sent".Dump();
}
catch (Exception ex)
{
ex.Dump();
}
}
public string GetText()
{
Thread.Sleep(250);
return "Test";
}
If you're set on keeping the continuation, I wonder if a slight structural refactoring in the continuation may help, both to push up creation of the events and to honor the await statements. Perhaps something similar to the following LinqPad snippet:
async Task Main()
{
while(true)
{
var t = Task.Factory.StartNew(() => GetText());
var _ = t.ContinueWith(async q =>
{
var events = new[] { new EventData(Encoding.UTF8.GetBytes(t.Result)) };
await Send(events).ConfigureAwait(false);
});
await Task.Yield();
}
}
public EventHubProducerClient client = new EventHubProducerClient("<< CONNECTION STRING >>");
public async Task Send(EventData[] events)
{
try
{
await client.SendAsync(events).ConfigureAwait(false);
"Sent".Dump();
}
catch (Exception ex)
{
ex.Dump();
}
}
public string GetText()
{
Thread.Sleep(250);
return "Test";
}
This is a followup question to the following question:
Volatile IEnlistmentNotification and TransactionScope.AsyncFlowEnabled = true
The approach accepted in the question above works as long as you don't await multiple statements. Let me show an example:
public class SendResourceManager : IEnlistmentNotification
{
private readonly Action onCommit;
public SendResourceManager(Action onCommit)
{
this.onCommit = onCommit;
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Commit(Enlistment enlistment)
{
Debug.WriteLine("Committing");
this.onCommit();
Debug.WriteLine("Committed");
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
}
public class AsyncTransactionalMessageSender : ISendMessagesAsync
{
private readonly List<Message> sentMessages = new List<Message>();
public IReadOnlyCollection<Message> SentMessages
{
get { return new ReadOnlyCollection<Message>(this.sentMessages); }
}
public async Task SendAsync(Message message)
{
if (Transaction.Current != null)
{
await Transaction.Current.EnlistVolatileAsync(
new SendResourceManager(async () => await this.SendInternal(message)),
EnlistmentOptions.None);
}
else
{
await this.SendInternal(message);
}
}
private async Task SendInternal(Message message)
{
Debug.WriteLine("Sending");
await Task.Delay(1000);
this.sentMessages.Add(message);
Debug.WriteLine("Sent");
}
}
[Test]
public async Task ScopeRollbackAsync_DoesntSend()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
// We do not commit the scope
}
sender.SentMessages.Should().BeEmpty();
}
[Test]
public async Task ScopeCompleteAsync_Sends()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
tx.Complete();
}
sender.SentMessages.Should().HaveCount(3)
.And.Contain(m => m.Value == "First")
.And.Contain(m => m.Value == "Second")
.And.Contain(m => m.Value == "Last");
}
As soon as you introduce a Task.Delay like shown in the example above the generated asynchronous statemachine will never come back and invoke the this.sentMessages.Add(message) and Debug.WriteLine("Sent")
The problem is I currently see now way to properly enlist asynchronous code inside the enlistment notification. Any ideas how to tackle this challenge?
I'm trying to create a quite simple notifications system (don't want to use SignalIR or something else). I have the following testing code:
Client side:
var source = new EventSource('/notifications.axd');
source.onopen = function () {
Console.log("Connection open");
};
source.onerror = function () {
Console.log("Connection error");
};
source.onmessage = function (event) {
Console.log("Message: " + event.data);
};
Server side:
public class NotificationMessage {
public NotificationMessage() {
Id = Guid.NewGuid().ToString();
}
public string Id { get; private set; }
}
public class NotificationsHandler : HttpTaskAsyncHandler {
private const string CONTENT_TYPE = "text/event-stream";
private sealed class NotificationItem {
public ConcurrentQueue<NotificationMessage> Messages;
public CancellationTokenSource CancellationTokenSource;
}
private static readonly ConcurrentDictionary<string, NotificationItem> _tasks =
new ConcurrentDictionary<string, NotificationItem>();
public static void Notify(string hostId, string userId, NotificationMessage message) {
NotificationItem item;
if (!_tasks.TryGetValue(string.Format("{0}|{1}", hostId, userId), out item)) {
return;
}
var tokenSource = item.CancellationTokenSource;
item.Messages.Enqueue(message);
item.CancellationTokenSource = new CancellationTokenSource();
tokenSource.Cancel();
}
public override async Task ProcessRequestAsync(HttpContext context) {
HttpRequest request = context.Request;
NotificationItem item = _tasks.GetOrAdd(
string.Format("{0}|{1}", request.Url.Host, CsSession.Data.CurrentUser.Id),
k => new NotificationItem {
Messages = new ConcurrentQueue<NotificationMessage>(),
CancellationTokenSource = new CancellationTokenSource()
}
);
HttpResponse response = context.Response;
response.ContentType = CONTENT_TYPE;
response.CacheControl = "no-cache";
response.ContentEncoding = Encoding.UTF8;
response.AppendHeader("connection", "keep-alive");
response.BufferOutput = false;
bool supportsAsyncFlush = response.SupportsAsyncFlush;
bool shouldPing = true;
while (response.IsClientConnected) {
try {
NotificationMessage message = null;
if ((!item.Messages.IsEmpty && item.Messages.TryDequeue(out message)) || shouldPing) {
response.Write(string.Format("data:{0}\n\n", message == null ? "{}" : JsonMapper.Serialize(message)));
if (supportsAsyncFlush) {
await Task.Factory.FromAsync(response.BeginFlush, response.EndFlush, null);
} else {
response.Flush();
}
}
} catch (Exception) {
break;
}
var delay = Task.Delay(15000, item.CancellationTokenSource.Token);
await delay;
shouldPing = delay.Status == TaskStatus.RanToCompletion;
}
}
}
The problem is: the above doesn't works. I have two issues:
1) When the client connects, I receive an empty packet (that's ok). Then, if I don't enqueue any messages, after awaiting the Task.Delay, the loop tries to write an empty message again, but I don't know where. The response.Write line never returns (and nothing is being received on the client).
2) If I write to the queue, for some reason the connection is dropped. If I put a breakpoint on the line after the await delay, that line is never executed (while my logic says otherwise :) ). If I cancel the token, the delay task should quit, but it seems it is aborting the whole handler??