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??
Related
I have an API call using client.GetAsync(url) within a SSIS script task but for some reason its not waiting for response from API and jumping back to the entry point for the script task which is public void Main(). Done some reading and found out that it might result in a deadlock for some reason but tried all the variations I could find to get it to work but with no luck. Something else that I don't understand is the exact same code is running on a webpage and that works perfect and waits for response from the api and continuing the flow.
Script Task entry point
The response here for payload is: ID =5, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
Here if in debug mode and moving the process back to go through the process again I noticed there 2 threads one current executing and the old one with the response I was expecting on the first call but not too sure what this means.
public void Main() {
// TODO: Add your code here
try {
PackageDetails packageInfo = new PackageDetails {
PackageNumber = 1234567891, Env = "Development", UserName = "USER"
};
var payload = API.ListHeadByPackAsync(packageInfo);
//var test = GetResponse();
Dts.TaskResult = (int) ScriptResults.Success;
} catch (Exception ex) {
System.Console.Write(ex.Message);
Dts.TaskResult = (int) ScriptResults.Failure;
}
}
API Call
public static class API {
public static async Task<PackageDetails> ListHeadByPackAsync(PackageDetails package) {
PackageDetails packageInfo = new PackageDetails();
try {
using(var client = new ApiClient(requestUrl, authToken)) {
var response = await client.GetAsync(); //-> not waiting for response
}
} catch (Exception err) {
switch (err.Message) {
//TODO:
}
}
return packageInfo;
}
}
Client
public class ApiClient: IDisposable {
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private readonly string _credentials;
//private const string MediaTypeXml = "application/csv";
public ApiClient(string baseUrl, string authToken, TimeSpan ? timeout = null) {
_baseUrl = baseUrl;
_credentials = Base64Encode(authToken);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task < string > GetAsync() {
EnsureHttpClientCreated();
using(var response = await _httpClient.GetAsync(_baseUrl).ConfigureAwait(continueOnCapturedContext: false))
//-> after executing above line it will go straight to public void Main(), dose not wait for response
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose() {
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient() {
_httpClientHandler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false) {
Timeout = _timeout
};
if (!string.IsNullOrWhiteSpace(_baseUrl)) {
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Add("Authorization", "Basic" + " " + _credentials);
}
private void EnsureHttpClientCreated() {
if (_httpClient == null) {
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
CreateHttpClient();
}
}
public static string Base64Encode(string token) {
var tokenBytes = System.Text.Encoding.UTF8.GetBytes(token);
return System.Convert.ToBase64String(tokenBytes);
}
}
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.
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!
I am developing Android app using ClientWebSocket.
When I start the app, everything works correctly if the server is running.
After I stop the server, the client can't be re-connected to the server and can't receive messages. I noticed that the status of clientwebsocket remains "Open" after I stop the server!
If I start the app then start the server, the client can't connect!
This is the code:
string ip = "192.168.1.142";
int port = 9000;
private const int ReceiveChunkSize = 1024;
private const int SendChunkSize = 1024;
private ClientWebSocket _ws;
private readonly Uri _uri;
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly CancellationToken _cancellationToken;
Preparing the client:
_ws = new ClientWebSocket ();
_ws.Options.KeepAliveInterval = TimeSpan.FromSeconds (20);
_uri = new Uri ("ws://"+ip+":"+port);
_cancellationToken = _cancellationTokenSource.Token;
Connection Method:
public async void ConnectAsync()
{
await Task.Run (() => {
try {
var tk = _ws.ConnectAsync (_uri, _cancellationTokenSource.Token);
tk.Wait(4000);
if (tk.IsCompleted) {
//CallOnConnected ();
StartListen ();
}
}
catch
{
}
});
}
Listen for income messages:
private async void StartListen()
{
var buffer = new byte[ReceiveChunkSize];
try
{
while (_ws.State == WebSocketState.Open)
{
var stringResult = new StringBuilder();
WebSocketReceiveResult result;
do
{
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
await
_ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
//CallOnDisconnected();
}
else
{
var str = Encoding.UTF8.GetString(buffer, 0, result.Count);
stringResult.Append(str);
}
} while (!result.EndOfMessage);
//CallOnMessage(stringResult);
}
}
catch (Exception)
{
//CallOnDisconnected();
}
finally
{
_ws.Dispose();
}
}
In the MainActivity, I use a timer for re-connect:
void Try_Connect(){
if ((my_clientwebsocket.Connection_Status == System.Net.WebSockets.WebSocketState.Open)|(my_clientwebsocket.Connection_Status == System.Net.WebSockets.WebSocketState.Connecting)) {
return;
}
new Thread (new ThreadStart (delegate {
RunOnUiThread (() => {
try {
my_clientwebsocket.ConnectAsync();
} catch {
}
});
})).Start ();
}
What is the wrong?!
Thanks for help
I have implemented IHttpAsyncHandler in my class to perform 5-6 long running process in background and acknowledge to client on start of each task.
Earlier I was using one session variable and updating it with current status of task, and giving async call request to server from jquery in interval of 5 seconds to get current status, but this implementation is not good because its continually hitting request to server for status.
Then I implemented IHttpAsyncHandler in my application, now server itself send acknowledgement to client, but as per my implementation I am able to send only one acknowledgement! if I try to send more than one then its giving error as "object reference not set to an instance of an object"
please check my sample code.
in my code
ExecuteFirst() method works fine sending acknowledgement to client but ExecuteSecond() does not send acknowledgement its giving error.
I goggled a lot but not getting proper way to send multiple acknowledgement to client.
this is my sample code, please help me if any one have any idea.
Javascript
function postRequest(url) {
var url = 'StartLongRunningProcess.ashx?JOBCODE=18'
var xmlhttp = getRequestObject();
xmlhttp.open("POST", url, true);
xmlhttp.onreadystatechange =
function () {
if (xmlhttp.readyState == 4) {
var response = xmlhttp.responseText;
divResponse.innerHTML += "<p>" + response + "</p>";
}
}
xmlhttp.send();
}
function getRequestObject() {
var req;
if (window.XMLHttpRequest && !(window.ActiveXObject)) {
try {
req = new XMLHttpRequest();
}
catch (e) {
req = false;
}
}
else if (window.ActiveXObject) {
try {
req = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
req = new ActiveXObject('Microsoft.XMLHTTP');
}
catch (e) {
req = false;
}
}
}
return req;
}
StartLongRunningProcess.ashx.cs
public class StartLongRunningProcess: IHttpAsyncHandler, IRequiresSessionState
{
private AsyncRequestResult _asyncResult;
public void ProcessRequest(HttpContext context) {}
public bool IsReusable
{
get { return true;}
}
public IAsyncResult BeginProcessRequest(HttpContext context, System.AsyncCallback cb, object extraData)
{
int32 jobCode= convert.ToInt32(context.Request["JOBCODE"]);
_asyncResult = new AsyncRequestResult(context, cb, jobCode);
if(jobCode==18)
{
StartProcess18()
}
else
{
//StartProcessOther()
}
}
private StartProcess18()
{
var task1= new Task(() =>
{
ExecuteFirst();
});
var task2 = task1.ContinueWith((t1) =>
{
ExecuteSecond();
}, TaskContinuationOptions.OnlyOnRanToCompletion);
task1.Start();
}
private ExecuteFirst()
{
//notify to client that this job has been started
_asyncResult.CurrentContext.Response.Write("First task has been started");
_asyncResult.Notify();
// Above line of code successfully send a acknowledgement to client
//Doing some long running process
}
private ExecuteSecond()
{
//notify to client that this job has been started
_asyncResult.CurrentContext.Response.Write("Second task has been started");
// Above line of code giving error and if I skip it and call Notify() this also does not work.
_asyncResult.Notify();
//Doing some long running process
}
public void EndProcessRequest(IAsyncResult result)
{
}
}
AsyncRequestResult.cs
public class AsyncRequestResult : IAsyncResult
{
private HttpContext context;
private AsyncCallback callback;
private ManualResetEvent completeEvent = null;
private object data;
private object objLock = new object();
private bool isComplete = false;
public AsyncRequestResult(HttpContext ctx, AsyncCallback cb, object d)
{
this.context = ctx;
this.callback = cb;
this.data = d;
}
public HttpContext Context
{
get { return this.context; }
}
public void Notify()
{
//isComplete = true;
lock(objLock)
{
if(completeEvent != null)
{
completeEvent.Set();
}
}
if (callback != null)
{
callback(this);
}
}
public object AsyncState
{
get { return this.data; }
}
public bool CompletedSynchronously
{
get { return false; }
}
public WaitHandle AsyncWaitHandle
{
get
{
lock(objLock)
{
if (completeEvent == null)
completeEvent = new ManualResetEvent(false);
return completeEvent;
}
}
}
public bool IsCompleted
{
get { return this.isComplete; }
}
}
HttpContext.Current is not null only if you access it in a thread that handles incoming requests.
Your continuation task that is running is most likely running on a ThreadPool thread without the HttpContext.Current flowing to the continuation, hence it being null.
Try setting your TaskScheduler to TaskScheduler.FromCurrentSynchronizationContext() in order to execute it in the same context.
private StartProcess18()
{
var task1= new Task(() =>
{
ExecuteFirst();
});
var task2 = task1.ContinueWith((t1) =>
{
ExecuteSecond();
}, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task1.Start();
}