I am processing a list of Proxy servers asynchronously/concurrently, testing each proxy server for validity. Those proxy servers are being displayed in a custom user control which inherits from DataGridView and sets the DoubleBuffered property to true in it's constructor. Furthermore, that DGV is not data-bound but rather using Virtual Mode and CellValueNeeded.
In my method which tests the validity of the displayed proxies (ProxyTester.Start()), I can control the degree of concurrency using a SemaphoreSlim object. When that semaphore is initialized with a small value like 10, I can scroll through the DGV and see the data being updated and it is smooth. If I increase the degree of concurrency to a larger number like 100, which increases throughput (Yay!), my DGV starts lagging during scrolling.
How can I reduce that lag during scrolling/processing the list while still having a high degree of concurrency, besides things I've already done like setting DoubleBuffered to True and using Virtual Mode?
public partial class ExtendedDataGridView : DataGridView
{
public ExtendedDataGridView()
{
//InitializeComponent();
DoubleBuffered = true;
}
}
public partial class DataGridViewForm : Form
{
private List<Proxy> proxies = new List<Proxy>();
public DataGridViewForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10000; i++)
{
proxies.Add(new Proxy("127.0.0." + RandomUtility.GetRandomInt(1, 5), 8888));
}
extendedDataGridView1.RowCount = proxies.Count;
}
private void button2_Click(object sender, EventArgs e)
{
var judges = new List<ProxyJudge>();
judges.Add(new ProxyJudge("http://azenv.net"));
Task.Run(async () => { await ProxyTester.Start(proxies, judges, maxConcurrency: 1); });
}
private void extendedDataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
if (proxies.Count > 0)
{
var proxy = proxies[e.RowIndex];
switch (e.ColumnIndex)
{
case 0:
e.Value = proxy.IP;
break;
case 1:
e.Value = proxy.IsValid;
break;
default:
break;
}
}
}
}
public class ProxyTester
{
public async static Task Start(List<Proxy> proxies, List<ProxyJudge> judges, List<ProxyTest> tests = null, PauseOrCancelToken pct = null, int maxConcurrency = 100)
{
if (tests == null)
{
tests = new List<ProxyTest>();
}
//Get external IP to check if proxy is anonymous.
var publicIp = await WebUtility.GetPublicIP();
//Validate proxy judges.
var tasks = new List<Task>();
var semaphore = new SemaphoreSlim(maxConcurrency);
foreach (var judge in judges)
{
tasks.Add(Task.Run(async () => {
await semaphore.WaitAsync();
judge.IsValid = await judge.TestValidityAsync();
if (pct != null) { await pct.PauseOrCancelIfRequested(); }
semaphore.Release();
}));
}
await Task.WhenAll(tasks);
var validJudges = from judge in judges
where judge.IsValid
select judge;
if (validJudges.Count() == 0)
{
throw new Exception("No valid judges loaded.");
}
//Validate proxy tests.
tasks.Clear();
foreach (var test in tests)
{
tasks.Add(Task.Run(async () => {
await semaphore.WaitAsync();
test.IsValid = await test.TestValidityAsync();
if (pct != null) { await pct.PauseOrCancelIfRequested(); }
semaphore.Release();
}));
}
await Task.WhenAll(tasks);
var validTests = from test in tests
where test.IsValid
select test;
//Test proxies with a random, valid proxy judge. If valid, test with all valid proxy tests.
tasks.Clear();
var count = 0;
foreach (var proxy in proxies)
{
tasks.Add(Task.Run(async () =>
{
await semaphore.WaitAsync();
proxy.IsValid = await proxy.TestValidityAsync(validJudges.ElementAt(RandomUtility.GetRandomInt(0, validJudges.Count())));
semaphore.Release();
Interlocked.Increment(ref count);
Console.WriteLine(count);
if (proxy.IsValid)
{
proxy.TestedSites.AddRange(validTests);
var childTasks = new List<Task>();
foreach (var test in validTests)
{
childTasks.Add(Task.Run(async () =>
{
await semaphore.WaitAsync();
proxy.TestedSites.ElementAt(proxy.TestedSites.IndexOf(test)).IsValid = await proxy.TestValidityAsync(test);
if (pct != null) { await pct.PauseOrCancelIfRequested(); }
semaphore.Release();
}));
}
await Task.WhenAll(childTasks);
}
}));
}
await Task.WhenAll(tasks);
}
}
Related
I am trying to develop a windows service to send notifications to subscriptions.
The data is saved in a SQL server database.
Notifications are created by making a web POST request to a REST API endpoint and saved in a database table.
The service starts one Task that keeps reading notifications from this db table and add them to a queue.
Also the service starts few Tasks that keep reading from the queue and do the actual send process.
The code is working good and doing the job needed, but the problem is that CPU usage is 100% when running the service.
I tried to use Thread.Sleep or Task.Delay but neither helped me to reduce the CPU usage.
I have read in this codeprojct page, that I need to use wait handlers and should wait on some condition. I could not have this working properly.
so can anyone advise what can I do to reduce CPU usage for EnqueueTask and DequeueTask ?
Here is the sender code:
static class NotificationSender
{
static ConcurrentQueue<NotificationDelivery> deliveryQueue = null;
static Task enqueueTask = null;
static Task[] dequeueTasks = null;
public static void StartSending(ServiceState serviceState)
{
PushService.InitServices();
enqueueTask = Task.Factory.StartNew(EnqueueTask, serviceState);
deliveryQueue = new ConcurrentQueue<NotificationDelivery>();
int dequeueTasksCount = 10;
dequeueTasks = new Task[dequeueTasksCount];
for (int i = 0; i < dequeueTasksCount; i++)
{
dequeueTasks[i] = Task.Factory.StartNew(DequeueTask, serviceState);
}
}
public static void EnqueueTask(object state)
{
ServiceState serviceState = (ServiceState)state;
using (DSTeckWebPushNotificationsContext db = new DSTeckWebPushNotificationsContext())
{
while (!serviceState.CancellationTokenSource.Token.IsCancellationRequested)
{
int toEnqueue = 100 - deliveryQueue.Count;
if (toEnqueue > 0)
{
// fetch some records from db to be enqueued
NotificationDelivery[] deliveries = db.NotificationDeliveries
.Include("Subscription")
.Include("Notification")
.Include("Notification.NotificationLanguages")
.Include("Notification.NotificationLanguages.Language")
.Where(nd => nd.Status == NotificationDeliveryStatus.Pending && DateTime.Now >= nd.StartSendingAt)
.OrderBy(nd => nd.StartSendingAt)
.Take(toEnqueue)
.ToArray();
foreach (NotificationDelivery delivery in deliveries)
{
delivery.Status = NotificationDeliveryStatus.Queued;
deliveryQueue.Enqueue(delivery);
}
if (deliveries.Length > 0)
{
db.SaveChanges(); // save Queued state, so not fetched again the next loop
}
}
// save any changes made by the DequeueTask
// an event may be used here to know if any changes made
db.SaveChanges();
}
Task.WaitAll(dequeueTasks);
db.SaveChanges();
}
}
public async static void DequeueTask(object state)
{
ServiceState serviceState = (ServiceState)state;
while (!serviceState.CancellationTokenSource.Token.IsCancellationRequested)
{
NotificationDelivery delivery = null;
if (deliveryQueue.TryDequeue(out delivery))
{
NotificationDeliveryStatus ns = NotificationDeliveryStatus.Pending;
if (delivery.Subscription.Status == SubscriptionStatus.Subscribed)
{
PushResult result = await PushService.DoPushAsync(delivery);
switch (result)
{
case PushResult.Pushed:
ns = NotificationDeliveryStatus.Delivered;
break;
case PushResult.Error:
ns = NotificationDeliveryStatus.FailureError;
break;
case PushResult.NotSupported:
ns = NotificationDeliveryStatus.FailureNotSupported;
break;
case PushResult.UnSubscribed:
ns = NotificationDeliveryStatus.FailureUnSubscribed;
delivery.Subscription.Status = SubscriptionStatus.UnSubscribed;
break;
}
}
else
{
ns = NotificationDeliveryStatus.FailureUnSubscribed;
}
delivery.Status = ns;
delivery.DeliveredAt = DateTime.Now;
}
}
}
public static void Wait()
{
Task.WaitAll(enqueueTask);
Task.WaitAll(dequeueTasks);
enqueueTask.Dispose();
for(int i = 0; i < dequeueTasks.Length; i++)
{
dequeueTasks[i].Dispose();
}
}
}
An object of type ServiceState is used to maintain starting and stopping the service, and here is the code for this type:
class ServiceState
{
public CancellationTokenSource CancellationTokenSource { get; set; }
public void Start()
{
CancellationTokenSource = new CancellationTokenSource();
NotificationSender.StartSending(this);
}
public void Stop()
{
CancellationTokenSource.Cancel();
NotificationSender.Wait();
CancellationTokenSource.Dispose();
}
}
Here is the service start and stop code:
protected override void OnStart(string[] args)
{
_serviceState = new ServiceState();
_serviceState.Start();
}
protected override void OnStop()
{
_serviceState.Stop();
}
I think I could finally do good changes to maintain the CPU usage using wait handlers and a timer.
EnqueueTask will wait 5 seconds before trying to fetch data again from the notifications table if no notifications fetched. If no notifications fetched, it will start the timer and reset the wait handle. The timer elapsed callback will then set the wait handle.
Also DequeueTask is now using a wait handle. If no more items in the queue, it will reset the wait handle to stop dequeue-ing empty queue. EnqueueTask will set this wait handle when it adds items to the queue.
CPU usage is now <= 10%
And here is the updated NotificationSender code:
static class NotificationSender
{
static ConcurrentQueue<NotificationDelivery> deliveryQueue = null;
static Task enqueueTask = null;
static Task[] dequeueTasks = null;
static ManualResetEvent enqueueSignal = null;
static ManualResetEvent dequeueSignal = null;
static System.Timers.Timer enqueueTimer = null;
public static void StartSending(CancellationToken token)
{
PushService.InitServices();
using (DSTeckWebPushNotificationsContext db = new DSTeckWebPushNotificationsContext())
{
NotificationDelivery[] queuedDeliveries = db.NotificationDeliveries
.Where(nd => nd.Status == NotificationDeliveryStatus.Queued)
.ToArray();
foreach (NotificationDelivery delivery in queuedDeliveries)
{
delivery.Status = NotificationDeliveryStatus.Pending;
}
db.SaveChanges();
}
enqueueSignal = new ManualResetEvent(true);
dequeueSignal = new ManualResetEvent(false);
enqueueTimer = new System.Timers.Timer();
enqueueTimer.Elapsed += EnqueueTimerCallback;
enqueueTimer.Interval = 5000;
enqueueTimer.AutoReset = false;
enqueueTimer.Stop();
enqueueTask = new Task(EnqueueTask, token, TaskCreationOptions.LongRunning);
enqueueTask.Start();
deliveryQueue = new ConcurrentQueue<NotificationDelivery>();
int dequeueTasksCount = 10;
dequeueTasks = new Task[dequeueTasksCount];
for (int i = 0; i < dequeueTasksCount; i++)
{
dequeueTasks[i] = new Task(DequeueTask, token, TaskCreationOptions.LongRunning);
dequeueTasks[i].Start();
}
}
public static void EnqueueTimerCallback(Object source, ElapsedEventArgs e)
{
enqueueSignal.Set();
enqueueTimer.Stop();
}
public static void EnqueueTask(object state)
{
CancellationToken token = (CancellationToken)state;
using (DSTeckWebPushNotificationsContext db = new DSTeckWebPushNotificationsContext())
{
while (!token.IsCancellationRequested)
{
if (enqueueSignal.WaitOne())
{
int toEnqueue = 100 - deliveryQueue.Count;
if (toEnqueue > 0)
{
// fetch some records from db to be enqueued
NotificationDelivery[] deliveries = db.NotificationDeliveries
.Include("Subscription")
.Include("Notification")
.Include("Notification.NotificationLanguages")
.Include("Notification.NotificationLanguages.Language")
.Where(nd => nd.Status == NotificationDeliveryStatus.Pending && DateTime.Now >= nd.StartSendingAt)
.OrderBy(nd => nd.StartSendingAt)
.Take(toEnqueue)
.ToArray();
foreach (NotificationDelivery delivery in deliveries)
{
delivery.Status = NotificationDeliveryStatus.Queued;
deliveryQueue.Enqueue(delivery);
}
if (deliveries.Length > 0)
{
// save Queued state, so not fetched again the next loop
db.SaveChanges();
// signal the DequeueTask
dequeueSignal.Set();
}
else
{
// no more notifications, wait 5 seconds before try fetching again
enqueueSignal.Reset();
enqueueTimer.Start();
}
}
// save any changes made by the DequeueTask
// an event may be used here to know if any changes made
db.SaveChanges();
}
}
Task.WaitAll(dequeueTasks);
db.SaveChanges();
}
}
public async static void DequeueTask(object state)
{
CancellationToken token = (CancellationToken)state;
while (!token.IsCancellationRequested)
{
if (dequeueSignal.WaitOne()) // block untill we have items in the queue
{
NotificationDelivery delivery = null;
if (deliveryQueue.TryDequeue(out delivery))
{
NotificationDeliveryStatus ns = NotificationDeliveryStatus.Pending;
if (delivery.Subscription.Status == SubscriptionStatus.Subscribed)
{
PushResult result = await PushService.DoPushAsync(delivery);
switch (result)
{
case PushResult.Pushed:
ns = NotificationDeliveryStatus.Delivered;
break;
case PushResult.Error:
ns = NotificationDeliveryStatus.FailureError;
break;
case PushResult.NotSupported:
ns = NotificationDeliveryStatus.FailureNotSupported;
break;
case PushResult.UnSubscribed:
ns = NotificationDeliveryStatus.FailureUnSubscribed;
delivery.Subscription.Status = SubscriptionStatus.UnSubscribed;
break;
}
}
else
{
ns = NotificationDeliveryStatus.FailureUnSubscribed;
}
delivery.Status = ns;
delivery.DeliveredAt = DateTime.Now;
}
else
{
// empty queue, no more items
// stop dequeueing untill new items added by EnqueueTask
dequeueSignal.Reset();
}
}
}
}
public static void Wait()
{
Task.WaitAll(enqueueTask);
Task.WaitAll(dequeueTasks);
enqueueTask.Dispose();
for(int i = 0; i < dequeueTasks.Length; i++)
{
dequeueTasks[i].Dispose();
}
}
}
I am testing the validity of a large list of proxy servers concurrently. During this testing, many exceptions are being raised and caught. Although I am doing the testing in a background thread, my UI becomes unresponsive unless I use a SemaphoreSlim object to control the concurrency.
I know this is a self imposed bottle neck, and when scaling with an even larger list of proxies to test, I was hoping there might be a better way to solve the problem.
private void ValidateProxiesButton_Click(object sender, EventArgs e)
{
new Thread(async () =>
{
Thread.CurrentThread.IsBackground = true;
await ValidateProxiesAsync(proxies, judges, tests, 10);
}).Start();
}
public async Task ValidateProxiesAsync(IEnumerable<Proxy> proxies, IEnumerable<ProxyJudge> judges, IEnumerable<ProxyTest> tests = null, int maxConcurrency = 20)
{
if (proxies.Count() == 0)
{
throw new ArgumentException("Proxy list empty.");
}
foreach (var proxy in proxies)
{
proxy.Status = ProxyStatus.Queued;
}
//Get external IP to check if proxy is anonymous.
var publicIp = await WebUtility.GetPublicIP();
foreach (var judge in judges)
{
judge.Invalidation = publicIp;
}
await ValidateTestsAsync(judges.ToList<IProxyTest>());
var validJudges = judges.ToList<IProxyTest>().GetValidTests();
if (validJudges.Count == 0)
{
throw new ArgumentException("No valid judges found.");
}
if (tests != null)
{
await ValidateTestsAsync(tests.ToList<IProxyTest>());
}
var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = new List<Task>();
foreach (var proxy in proxies)
{
tasks.Add(Task.Run(async () =>
{
await semaphore.WaitAsync();
proxy.Status = ProxyStatus.Testing;
var isValid = await proxy.TestValidityAsync((IProxyTest)validJudges.GetRandomItem());
proxy.Status = isValid ? ProxyStatus.Valid : ProxyStatus.Invalid;
semaphore.Release();
}));
}
await Task.WhenAll(tasks);
}
Inside proxy.TestValidityAsync method
public async Task<bool> TestValidityAsync(IProxyTest test, int timeoutSeconds = 30)
{
try
{
var req = WebRequest.Create(test.URL);
req.Proxy = new WebProxy(this.ToString());
var respBody = await WebUtility.GetResponseStringAsync(req).TimeoutAfter(new TimeSpan(0, 0, timeoutSeconds));
if (respBody.Contains(test.Validation))
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
return false;
}
}
So I found a working solution, it is to add the TPL Dataflow NuGet package to my project and then use the TransformBlock class. When I do this, my UI stays very responsive even if I am processing tons of concurrent requests that often throw exceptions. The code below is proof of concept, I will update it when I translate it to work with my project.
Source: Throttling asynchronous tasks
private async void button1_Click(object sender, EventArgs e)
{
var downloader = new TransformBlock<string, WebResponse>(
url => Download(url),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 200 }
);
var buffer = new BufferBlock<WebResponse>();
downloader.LinkTo(buffer);
var urls = new List<string>();
for (int i = 0; i < 100000; i++)
{
urls.Add($"http://example{i}.com");
}
foreach (var url in urls)
downloader.Post(url);
//or await downloader.SendAsync(url);
downloader.Complete();
await downloader.Completion;
IList<WebResponse> responses;
if (buffer.TryReceiveAll(out responses))
{
//process responses
}
}
private WebResponse Download(string url)
{
WebResponse resp = null;
try
{
var req = WebRequest.Create(url);
resp = req.GetResponse();
}
catch (Exception)
{
}
return resp;
}
}
I am new to Dataflow, and I follow this walkthrough How to: Cancel a Dataflow Block.
I click add button first, and then click cancel, but I got exception about "A task was canceled Exception" after clicking cancel button. I fail to find any way to resolve this error.
Any help would be appreciated.
Update:
Code for demo:
public partial class Form1 : Form
{
CancellationTokenSource cancellationTokenSource;
TransformBlock<WorkItem, WorkItem> startWork;
ActionBlock<WorkItem> completeWork;
ActionBlock<ToolStripProgressBar> incProgress;
ActionBlock<ToolStripProgressBar> decProgress;
TaskScheduler uiTaskScheduler;
public Form1()
{
InitializeComponent();
uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Cancel.Enabled = false;
}
private void Add_Click(object sender, EventArgs e)
{
if (!Cancel.Enabled)
{
CreatePipeline();
Cancel.Enabled = true;
}
for (int i = 0; i < 20; i++)
{
toolStripProgressBar1.Value++;
startWork.Post(new WorkItem());
}
}
private async void Cancel_Click(object sender, EventArgs e)
{
Add.Enabled = false;
Cancel.Enabled = false;
cancellationTokenSource.Cancel();
try
{
await Task.WhenAll(
completeWork.Completion,
incProgress.Completion,
decProgress.Completion);
}
catch (OperationCanceledException)
{
throw;
}
toolStripProgressBar4.Value += toolStripProgressBar1.Value;
toolStripProgressBar4.Value += toolStripProgressBar2.Value;
// Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0;
toolStripProgressBar2.Value = 0;
// Enable the Add Work Items button.
Add.Enabled = true;
}
private void CreatePipeline()
{
cancellationTokenSource = new CancellationTokenSource();
startWork = new TransformBlock<WorkItem, WorkItem>(workItem =>
{
workItem.DoWork(250, cancellationTokenSource.Token);
decProgress.Post(toolStripProgressBar1);
incProgress.Post(toolStripProgressBar2);
return workItem;
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationTokenSource.Token
});
completeWork = new ActionBlock<WorkItem>(workItem =>
{
workItem.DoWork(1000, cancellationTokenSource.Token);
decProgress.Post(toolStripProgressBar2);
incProgress.Post(toolStripProgressBar3);
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationTokenSource.Token,
MaxDegreeOfParallelism = 2
});
startWork.LinkTo(completeWork);
startWork.Completion.ContinueWith(delegate { completeWork.Complete(); },cancellationTokenSource.Token);
incProgress = new ActionBlock<ToolStripProgressBar>(progress =>
{
progress.Value++;
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationTokenSource.Token,
TaskScheduler = uiTaskScheduler
});
decProgress = new ActionBlock<ToolStripProgressBar>(progress => progress.Value--,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationTokenSource.Token,
TaskScheduler = uiTaskScheduler
});
}
class WorkItem
{
public void DoWork(int milliseconds, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested == false)
{
Thread.Sleep(milliseconds);
}
}
}
}
As #SirRufo pointed out, the solution to your question is simply don't re-throw the exception after you've caught it. But to highlight some of the other techniques you can use with dataflow as discussed in the comments I put together a small sample. I've tried to keep the spirit and intent of your original code intact. To that end; the original code didn't show how the flow would complete normally, as opposed to cancelled, so I left it out here as well.
using System;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
namespace WindowsFormsApp1 {
public partial class Form1 : Form {
private CancellationTokenSource cancellationTokenSource;
private TransformBlock<WorkItem, WorkItem> startWork;
private ActionBlock<WorkItem> completeWork;
private IProgress<int> progressBar1Value;
private IProgress<int> progressBar2Value;
public Form1() {
InitializeComponent();
btnCancel.Enabled = false;
}
private async void btnAdd_Click(object sender, EventArgs e) {
if(!btnCancel.Enabled) {
CreatePipeline();
btnCancel.Enabled = true;
}
var data = Enumerable.Range(0, 20).Select(_ => new WorkItem());
foreach(var workItem in data) {
await startWork.SendAsync(workItem);
progressBar1.Value++;
}
}
private async void btnCancel_Click(object sender, EventArgs e) {
btnAdd.Enabled = false;
btnCancel.Enabled = false;
cancellationTokenSource.Cancel();
await completeWork.Completion.ContinueWith(tsk => this.Invoke(new Action(() => this.Text = "Flow Cancelled")),
TaskContinuationOptions.OnlyOnCanceled);
progressBar4.Value += progressBar1.Value;
progressBar4.Value += progressBar2.Value;
// Reset the progress bars that track the number of active work items.
progressBar1.Value = 0;
progressBar2.Value = 0;
// Enable the Add Work Items button.
btnAdd.Enabled = true;
}
private void CreatePipeline() {
cancellationTokenSource = new CancellationTokenSource();
progressBar1Value = new Progress<int>(_ => progressBar1.Value++);
progressBar2Value = new Progress<int>(_ => progressBar2.Value++);
startWork = new TransformBlock<WorkItem, WorkItem>(async workItem => {
await workItem.DoWork(250, cancellationTokenSource.Token);
progressBar1Value.Report(0); //Value is ignored since the progressbar value is simply incremented
progressBar2Value.Report(0); //Value is ignored since the progressbar value is simply incremented
return workItem;
},
new ExecutionDataflowBlockOptions {
CancellationToken = cancellationTokenSource.Token
});
completeWork = new ActionBlock<WorkItem>(async workItem => {
await workItem.DoWork(1000, cancellationTokenSource.Token);
progressBar1Value.Report(0); //Value is ignored since the progressbar value is simply incremented
progressBar2Value.Report(0); //Value is ignored since the progressbar value is simply incremented
},
new ExecutionDataflowBlockOptions {
CancellationToken = cancellationTokenSource.Token,
MaxDegreeOfParallelism = 2
});
startWork.LinkTo(completeWork, new DataflowLinkOptions() { PropagateCompletion = true });
}
}
public class WorkItem {
public async Task DoWork(int milliseconds, CancellationToken cancellationToken) {
if(cancellationToken.IsCancellationRequested == false) {
await Task.Delay(milliseconds);
}
}
}
}
After checking the code, I released that the tasks will be cancelled if I click Cancel.
await Task.WhenAll(
completeWork.Completion,
incProgress.Completion,
decProgress.Completion);
But, above code Task.WhenAll need all of the tasks return complete status, then the "A task was canceled Exception" throw as expected if it returned cancelled instead of completed.
For a possible way to resolve this issue, we should return Task completed if we cancelled the task, and the code below works for me.
await Task.WhenAll(
completeWork.Completion.ContinueWith(task => cancelWork(task, "completeWork"), TaskContinuationOptions.OnlyOnCanceled),
incProgress.Completion.ContinueWith(task => cancelWork(task, "incProgress"), TaskContinuationOptions.OnlyOnCanceled),
decProgress.Completion.ContinueWith(task => cancelWork(task, "decProgress"), TaskContinuationOptions.OnlyOnCanceled));
Is it reasonable?
I have a function called getMessages that can be called by a Button click (using the RelayCommand trigger) or that is called in a timer every 15s.
The desired behavior is:
webservice > deserialize answer > system notification > updatelistview > insert localDB
But when the function is called by the timer the updatelistview is not done. Why does this happen if the function is the same and works perfectly in the button command?
CODE:
// Get messages for the logged in user
public async void getMessages()
{
try
{
List<FriendGetMessage> msg = new List<FriendGetMessage>();
var response = await CommunicationWebServices.GetCHAT("users/" + au.idUser + "/get", au.token);
if (response.StatusCode == HttpStatusCode.OK) // If there are messages for me.
{
var aux = await response.Content.ReadAsStringAsync();
IEnumerable<FriendGetMessage> result = JsonConvert.DeserializeObject<IEnumerable<FriendGetMessage>>(aux);
if (result != null)
{
foreach (var m in result)
{
msg.Add(m);
}
//MsgList=msg;
foreach (var f in Friends)
{
if (f.msg == null || f.msg.Count() == 0)
{
f.msg = new ObservableCollection<Messages>();
}
foreach (var mess in msg)
{
if (mess.idUser == f.idUser)
{
Messages mm = new Messages();
mm.received = mess.message;
mm.timestamp = "Received " + mess.serverTimestamp;
mm.align = "Right";
// Add to the friend list.
f.msg.Add(mm);
// Add to Local DB
InsertMessage(null, au.idUser.ToString(), f.idUser, mess.message, mess.serverTimestamp);
var notification = new System.Windows.Forms.NotifyIcon()
{
Visible = true,
Icon = System.Drawing.SystemIcons.Information,
BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info,
BalloonTipTitle = "New Message from " + f.name,
BalloonTipText = "Message: " + mess.message,
};
// Display for 5 seconds.
notification.ShowBalloonTip(5);
// The notification should be disposed when you don't need it anymore,
// but doing so will immediately close the balloon if it's visible.
notification.Dispose();
}
}
}
counterChat = 1; // resets the counter
}
}
else {
counterChat = counterChat * 2;
}
//var sql = "select * from chat";
//var respo = GetFromDatabase(sql);
OnPropertyChanged("Friends");
}
catch (Exception e)
{
MessageBox.Show("GetMessages: " + e);
Debug.WriteLine("{0} Exception caught.", e);
}
}
CODE TIMER:
public void chatUpdate()
{
_timerChat = new DispatcherTimer(DispatcherPriority.Render);
_timerChat.Interval = TimeSpan.FromSeconds(15);
_timerChat.Tick += new EventHandler(timerchat_Tick);
_timerChat.Start();
}
public void timerchat_Tick(object sender, EventArgs e)
{
if (counterChat != incChat)
{
incChat++;
}
else
{
getMessages();
OnPropertyChanged("Friends");
incChat = 0;
}
}
ADDED - I've also tried this and didn't worked (it seems that is some kind of concurrency problem to the ObservableCollection called Friends (is a friendslist) each friend has an ObservableCollection of messages (is a chat))
public void chatUpdate()
{
_timerChat = new DispatcherTimer(DispatcherPriority.Render);
_timerChat.Interval = TimeSpan.FromSeconds(15);
_timerChat.Tick += new EventHandler(timerchat_Tick);
_timerChat.Start();
}
public async void timerchat_Tick(object sender, EventArgs e)
{
if (counterChat != incChat)
{
incChat++;
}
else
{
Application.Current.Dispatcher.Invoke((Action)async delegate { await getMessages(); });
incChat = 0;
}
}
Best regards,
I think you need to make the timer handler be an async method as follows:
public async void timerchat_Tick(object sender, EventArgs e)
{
if (counterChat != incChat)
{
incChat++;
}
else
{
await getMessages();
OnPropertyChanged("Friends");
incChat = 0;
}
}
This way OnPropertyChanged("Friends") is guaranteed to fire after the work in getMessages is done.
The methods need to change to:
DispatcherTimer _timerChat = new DispatcherTimer(DispatcherPriority.Render);
_timerChat.Interval = TimeSpan.FromSeconds(15);
_timerChat.Tick += new EventHandler(timerchat_Tick);
_timerChat.Start();
public async void timerchat_Tick(object sender, EventArgs e)
{
//...
await getMessages();
//...
}
public async Task getMessages()
{
try
{
// ... your code here
string result = await response.Content.ReadAsStringAsync();
// .... rest of your code
}
catch (Exception e)
{
MessageBox.Show("GetMessages: " + e);
}
}
It is solved. The problem was in my ViewModels I was opening multiple threads and sometimes the right one would update the UI and sometimes no.
Thanks for all the answers.
I Want to write my own control, when the ctor is invoked, a MessageBox is shown.
public class Class1
{
public Class1()
{
ShowDialog();
}
void ShowDialog()
{
SynchronizationContext context = SynchronizationContext.Current;
if (context != null)
{
context.Post((f) =>
{
MessageDialog dialog = new MessageDialog("Hello!");
dialog.ShowAsync();
}, null);
}
}
}
If my class is used by someone, and write the codes as below, UnauthorizedAccessException is always thrown in dialog.ShowAsync();
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ClassLibrary1.Class1 c1 = new ClassLibrary1.Class1();
MessageDialog dialog1 = new MessageDialog("");
dialog1.ShowAsync();
}
Is there a way to show a message dialog without exception?
I found a way, enjoy it!
Task ShowDialog()
{
CoreDispatcher dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
Func<object, Task<bool>> action = null;
action = async (o) =>
{
try
{
if (dispatcher.HasThreadAccess)
await new MessageDialog("Hello!").ShowAsync();
else
{
dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => action(o));
}
return true;
}
catch (UnauthorizedAccessException)
{
if (action != null)
{
Task.Delay(500).ContinueWith(async t => await action(o));
}
}
return false;
};
return action(null);
}
As MessageDialogue needs to run on the UI thread, can you try switching it to:
var dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
dispatcher.RunAsync(DispatcherPriority.Normal, <lambda for your code which should run on the UI thread>);
The cleaner solution may look like this. The interesting part ist hidden in die showDialogAsync(). For convenience you can use the Close() method to close the current dialog again programmatically. The IUIDispatcher is another helper interface you can rebuild yourself easily:
private readonly IUIDispatcher _dispatcher;
readonly Object _queueMonitor = new object();
readonly Object _showMonitor = new object();
private IAsyncOperation<IUICommand> _currentDialogOperation;
readonly Queue<MessageDialog> _dialogQueue = new Queue<MessageDialog>();
public async Task ShowAsync(string content)
{
var md = new MessageDialog(content);
await showDialogAsync(md);
}
public async Task ShowAsync(string content, string caption)
{
var md = new MessageDialog(content, caption);
await showDialogAsync(md);
}
public async Task<MessageDialogResult> ShowAsync(string content, MessageDialogType dialogType)
{
var messageDialogResult = await ShowAsync(content, null, dialogType);
return messageDialogResult;
}
public async Task<MessageDialogResult> ShowAsync(string content, string caption, MessageDialogType dialogType)
{
var result = MessageDialogResult.Ok;
var md = string.IsNullOrEmpty(caption) ? new MessageDialog(content) : new MessageDialog(content, caption);
switch (dialogType)
{
case MessageDialogType.Ok:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
md.CancelCommandIndex = 0;
md.DefaultCommandIndex = 0;
break;
case MessageDialogType.OkCancel:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
case MessageDialogType.YesNo:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
case MessageDialogType.YesNoCancel:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
default:
throw new ArgumentOutOfRangeException("dialogType");
}
await showDialogAsync(md);
return result;
}
/// <summary>
/// Shows the dialogs, queued and one after the other.
/// We need this as a workaround for the the UnauthorizedAcsess exception.
/// </summary>
/// <param name="messageDialog">The message dialog.</param>
/// <returns></returns>
async Task showDialogAsync(MessageDialog messageDialog)
{
//Calls this function in a separated task to avoid ui thread deadlocks.
await Task.Run(async () =>
{
lock (_queueMonitor)
{
_dialogQueue.Enqueue(messageDialog);
}
try
{
while (true)
{
MessageDialog nextMessageDialog;
lock (_queueMonitor)
{
if (_dialogQueue.Count > 1)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Next dialog is waiting for MessageDialog to be accessable!!");
Monitor.Wait(_queueMonitor); //unlock and wait - regains lock after waiting
}
nextMessageDialog = _dialogQueue.Peek();
}
var showing = false;
_dispatcher.Execute(async () =>
{
try
{
lock (_showMonitor)
{
showing = true;
_currentDialogOperation = nextMessageDialog.ShowAsync();
}
await _currentDialogOperation;
lock (_showMonitor)
_currentDialogOperation = null;
}
catch (Exception e)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | " + e);
}
lock (_showMonitor)
{
showing = false;
Monitor.Pulse(_showMonitor); //unlock and wait - regains lock after waiting
}
});
lock (_showMonitor)
{
if (showing)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Waiting for MessageDialog to be closed!!");
//we must wait here manually for the closing of the dialog, because the dispatcher does not return a waitable task.
Monitor.Wait(_showMonitor); //unlock and wait - regains lock after waiting
}
}
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | MessageDialog was closed.");
return true;
}
}
finally
{
//make sure we leave this in a clean state
lock (_queueMonitor)
{
_dialogQueue.Dequeue();
Monitor.Pulse(_queueMonitor);
}
}
});
}
public void Close(string keyContent="")
{
try
{
if (keyContent.IsNullOrEmpty())
{
lock (_showMonitor)
{
if (_currentDialogOperation == null) return;
_currentDialogOperation.Cancel();
_currentDialogOperation = null;
}
}
else
{
var cancel = false;
lock (_queueMonitor)
{
if (_dialogQueue.Count == 0)
return;
var currentDialog = _dialogQueue.Peek();
Debug.WriteLine("MessageDialogService.cs | Close | {0}", currentDialog.Content);
if (currentDialog.Content == keyContent)
{
cancel = true;
}
}
if (!cancel) return;
lock (_showMonitor)
{
if (_currentDialogOperation == null) return;
_currentDialogOperation.Cancel();
_currentDialogOperation = null;
}
}
}
catch (Exception e)
{
Debug.WriteLine("MessageDialogService.cs | Close | " + e);
}
}
I think I've found it. I had the same problem when creating messageboxes in any other threads besides the main thread.
This is the C++ solution but I think you can convert it easily ;)
IAsyncOperation<IUICommand^> ^Op = msgbox->ShowAsync();
task<IUICommand^>( Op ).then([=](IUICommand^ C)
{
}).then([](task<void> t)
{
try
{
t.get();
}
catch (Platform::Exception ^e)
{
//ERROR!
}
});
On a side note this is the correct way to handle ANY WinRT/Windows 8 Store C++ exception.
You can always use
Execute.OnUIThread( async () => {
...
var dialog = new MessageDialog(yourMessage);
await dialog.ShowAsync();
...
});
This doesn't solve race conditions in the UI if you are trying to launch multiple dialogs from background threads. But you could use a try/catch to make sure you cover for that case.