I'm testing a method involving a SemaphoreSlim to notify when an event has occured. It works as intended, but I've noticed that if I use this within the first few seconds of my application starting there is a large delay between the release of the semaphore and the continuation of my code waiting for the semaphore.
main:
var userIntentProvider = new UserIntentProvider();
Console.WriteLine("started");
Task.Run(() =>
{
Console.WriteLine("starting");
//wait for an event to be added.
userIntentProvider.GetNextUserIntentAsync().Wait();
});
while (true)
{
var input = Console.ReadLine();
//add the event
userIntentProvider.AddUserIntent("bar");
}
class:
public void AddUserIntent(string userIntent)
{
NewUserIntent?.Invoke(this, userIntent);
}
public async Task GetNextUserIntentAsync(CancellationToken token = new CancellationToken())
{
var sw = new Stopwatch();
//create signal
var signal = new SemaphoreSlim(0, 1);
//create event handler
EventHandler<string> eventHandler = null;
eventHandler = (sender, e) =>
{
NewUserIntent -= eventHandler;
signal.Release(); //THE SEMAPHORE IS RELEASED AND THE STOPWATCH STARTED
sw.Start();
};
//wrap in try catch to force removal of event
try
{
//subscribe
NewUserIntent += eventHandler;
// wait for signal
await signal.WaitAsync();//THE SEMAPHORE IS WAITED ON AND THE STOPWATCH STOPPED
sw.Stop();
Console.WriteLine("done" + sw.ElapsedMilliseconds);
}
finally
{
NewUserIntent -= eventHandler;
}
}
The Stopwatch measuring the time between the Release() and await WaitAsync() calls can be up to 1.5s. If I wait for a few seconds after starting the application and then trigger this mechanism the stopwatch reports 0ms have passed (more like I was expecting) what could be causing this delay?
Edit: This is running in a console app.
Related
I'm implementing a Client who can ask a service for a certain action and also an abort button for this action.
Once I run the action using background thread the abort button should become active, but instead the entire GUI is stuck with the mouse icon as hour glass (Should mention that the action is still indeed occurring).
private void actionButton_Click(object sender, EventArgs e)
{
Run(RunMode.Action);
}
private void Run(RunMode runMode)
{
abortButton.Enabled = true;
try
{
var name = "ds_file";
var url = UrlProvider.BuildRequestUrl(runMode, name);
StartLoading($"Running request: {url}");
RunWorker(url);
}
catch (Exception ex)
{
AddToLog(ex.ToString());
PopError("Failed to run, see error in log box");
}
}
private void RunWorker(string url)
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
DatabaseHelper.DisableAllJobs();
HttpRequestsHandler.HttpGet(url);
DatabaseHelper.EnableRegularJobs();
};
worker.RunWorkerCompleted += (sender, args) =>
{
StopLoading();
abortButton.Enabled = false;
if (args.Error != null)
{
PopError("Failed to run, see error in log box");
AddToLog(args.Error.ToString());
}
else
{
PopInfo("Completed successfully");
}
};
worker.RunWorkerAsync();
}
What am I doing wrong?
Thanks
Following example run background service every 10 seconds to update GUI. You can modify it as you wish. By running your thread as async task your GUI never get hang.
public frm_testform()
{
InitializeComponent();
dispatcherTimer_Tick().DoNotAwait();
}
private async Task dispatcherTimer_Tick()
{
DispatcherTimer timer = new DispatcherTimer();
TaskCompletionSource<bool> tcs = null;
EventHandler tickHandler = (s, e) => tcs.TrySetResult(true);
timer.Interval = TimeSpan.FromSeconds(10);
timer.Tick += tickHandler;
timer.Start();
while (true)
{
tcs = new TaskCompletionSource<bool>();
await Task.Run(() =>
{
// Run your background service and UI update here
await tcs.Task;
}
}
It indeed turns out I had controls.enable = false in some part of the code (I really thought it totally meant for something else), thank you all for your help!!
I want to show in textbox the time execution of a function that is called using Task.Run, since takes some time
to complete and I'd created a thread for that.
The issue is when I click on begin button is printed inmediately the time in textBox1 and I want to show the
Elapsed time but only just after MyFunction completes the processing or when Cancel button is pressed.
Where should go sw.Stop()?
My current code for Begin and cancel button is:
void Begin_Click(object sender, EventArgs e)
{
Stopwatch sw = Stopwatch.StartNew();
// Pass the token to the cancelable operation.
cts = new CancellationTokenSource();
Task.Run(() => MyFunction(inputstring, cts.Token), cts.Token);
sw.Stop();
textBox1.Text += Math.Round(sw.Elapsed.TotalMilliseconds / 1000, 4) + " sec";
}
void Cancel_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts.Cancel();
cts = null;
}
}
You're not waiting for the MyFunction to complete, You're simply calculating the start time of the Task.Run call. To wait for the MyFunction to complete, you can await the Task returned by Task.Run.
async void Begin_Click(object sender, EventArgs e)//<--Note the async keyword here
{
Stopwatch sw = Stopwatch.StartNew();
// Pass the token to the cancelable operation.
cts = new CancellationTokenSource();
await Task.Run(() => MyFunction(inputstring, cts.Token), cts.Token);//<--Note the await keyword here
sw.Stop();
textBox1.Text += Math.Round(sw.Elapsed.TotalMilliseconds / 1000, 4) + " sec";
}
If you're new to asynchronous programming start by reading here and here
I am struggling with threading.
The problem is when I am iterating trough foreach loop.
When setting this.Document, the application performs login, that is triggered with an event and takes few seconds to complete. In the worker_RunWorkerCompleted method I need to perform some actions that depend on current login information.
The problem is that before I can perform this action for the first file, the this.Document already changes making the application perform another login. This way I can never actually perform my actions.
My question is: How can I pause the next thread until previous thread has completed.
Is there any other solution to my problem?
I tried with AutoResetEvent but I got no luck. I set waitOne() just after the RunWorkerAsync call and .Set() in the RunWorkerCompleted. The code never gets to RunWorkerCompleted...
Here is the code:
public void Start(object obj)
{
try
{
foreach (KeyValuePair<string, Stream> pair in this.CollectionOfFiles)
{
Worker = new BackgroundWorker();
Worker.DoWork += new DoWorkEventHandler(worker_DoWork);
Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
using (Stream stream = pair.Value)
{
primaryDocument = new Document(stream);
DataHolderClass dataHolder = new DataHolderClass();
dataHolder.FileName = pair.Key;
dataHolder.Doc = secondaryDocument;
//background thread call
Worker.RunWorkerAsync(dataHolder);
}
}
}
catch (Exception ex)
{
// exception logic
}
finally
{
// complete logic
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
DataHolderClass dataHolder = ((DataHolderClass)e.Argument);
// setting this attribute triggers execution of login event
this.Document = dataHolder.Doc;
e.Result = (dataHolder);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// here I need to perform some actions that are depending on the current login
DataHolderClass dataHolder = ((DataHolderClass)e.Result);
this.eventAggregator.GetEvent<ActionEvent>().Publish(new Message(EMessageType.Info) { Title = dataHolder.FileName });
}
no9,
Try the following:
System.Threading.ManualResetEvent _busy = new System.Threading.ManualResetEvent(false);
void ResumeWorker()
{
// Start the worker if it isn't running
if (!backgroundWorker1.IsBusy) backgroundWorker1.RunWorkerAsync(dataHolder);
// Unblock the worker
_busy.Set();
}
void PauseWorker()
{
// Block the worker
_busy.Reset();
}
void CancelWorker()
{
if (backgroundWorker1.IsBusy) {
// Set CancellationPending property to true
backgroundWorker1.CancelAsync();
// Unblock worker so it can see that
_busy.Set();
}
}
then in your code run the method.
Let me know if it works :)
class SimpleWaitPulse
{
static readonly object _locker = new object();
static bool _go;
static void Main()
{ // The new thread will block
new Thread (Work).Start(); // because _go==false.
Console.ReadLine(); // Wait for user to hit Enter
lock (_locker) // Let's now wake up the thread by
{ // setting _go=true and pulsing.
_go = true;
Monitor.Pulse (_locker);
}
}
static void Work()
{
lock (_locker)
while (!_go)
Monitor.Wait (_locker); // Lock is released while we’re waiting
Console.WriteLine ("Woken!!!");
}
}
Can you use pulse ?
Taken from : Threading in C# from albahari.com
Well, the design is terrible... but if you need to stick to it, you can set wait handles in a previous worker and wait for it in next. This is the minimal fix, still quite an abomination:
public void Start(object obj)
{
try
{
BackgroundWorker previousWorker = null;
DataHolderClass previousWorkerParams = null;
foreach (KeyValuePair<string, Stream> pair in this.CollectionOfFiles)
{
// signal event on previous worker RunWorkerCompleted event
AutoResetEvent waitUntilCompleted = null;
if (previousWorker != null)
{
waitUntilCompleted = new AutoResetEvent(false);
previousWorker.RunWorkerCompleted += (o, e) => waitUntilCompleted.Set();
// start the previous worker
previousWorker.RunWorkerAsync(previousWorkerParams);
}
Worker = new BackgroundWorker();
Worker.DoWork += (o, e) =>
{
// wait for the handle, if there is anything to wait for
if (waitUntilCompleted != null)
{
waitUntilCompleted.WaitOne();
waitUntilCompleted.Dispose();
}
worker_DoWork(o, e);
};
using (Stream stream = pair.Value)
{
primaryDocument = new Document(stream);
DataHolderClass dataHolder = new DataHolderClass();
dataHolder.FileName = pair.Key;
dataHolder.Doc = secondaryDocument;
// defer running this worker; we don't want it to finish
// before adding additional completed handler
previousWorkerParams = dataHolder;
}
previousWorker = Worker;
}
if (previousWorker != null)
{
previousWorker.RunWorkerAsync(previousWorkerParams);
}
}
catch (Exception ex)
{
// exception logic
}
finally
{
// complete logic
}
}
I have a list Uri's that I want "clicked" To achieve this I"m trying to create a new web-browser control per Uri. I create a new thread per Uri. The problem I'm having is the thread end before the document is fully loaded, so I never get to make use of the DocumentComplete event. How can I overcome this?
var item = new ParameterizedThreadStart(ClicIt.Click);
var thread = new Thread(item) {Name = "ClickThread"};
thread.Start(uriItem);
public static void Click(object o)
{
var url = ((UriItem)o);
Console.WriteLine(#"Clicking: " + url.Link);
var clicker = new WebBrowser { ScriptErrorsSuppressed = true };
clicker.DocumentCompleted += BrowseComplete;
if (String.IsNullOrEmpty(url.Link)) return;
if (url.Link.Equals("about:blank")) return;
if (!url.Link.StartsWith("http://") && !url.Link.StartsWith("https://"))
url.Link = "http://" + url.Link;
clicker.Navigate(url.Link);
}
You have to create an STA thread that pumps a message loop. That's the only hospitable environment for an ActiveX component like WebBrowser. You won't get the DocumentCompleted event otherwise. Some sample code:
private void runBrowserThread(Uri url) {
var th = new Thread(() => {
var br = new WebBrowser();
br.DocumentCompleted += browser_DocumentCompleted;
br.Navigate(url);
Application.Run();
});
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
var br = sender as WebBrowser;
if (br.Url == e.Url) {
Console.WriteLine("Natigated to {0}", e.Url);
Application.ExitThread(); // Stops the thread
}
}
Here is how to organize a message loop on a non-UI thread, to run asynchronous tasks like WebBrowser automation. It uses async/await to provide the convenient linear code flow and loads a set of web pages in a loop. The code is a ready-to-run console app which is partially based on this excellent post.
Related answers:
https://stackoverflow.com/a/22262976/1768303
https://stackoverflow.com/a/21775343/1768303
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApplicationWebBrowser
{
// by Noseratio - https://stackoverflow.com/users/1768303/noseratio
class Program
{
// Entry Point of the console app
static void Main(string[] args)
{
try
{
// download each page and dump the content
var task = MessageLoopWorker.Run(DoWorkAsync,
"http://www.example.com", "http://www.example.net", "http://www.example.org");
task.Wait();
Console.WriteLine("DoWorkAsync completed.");
}
catch (Exception ex)
{
Console.WriteLine("DoWorkAsync failed: " + ex.Message);
}
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
// navigate WebBrowser to the list of urls in a loop
static async Task<object> DoWorkAsync(object[] args)
{
Console.WriteLine("Start working.");
using (var wb = new WebBrowser())
{
wb.ScriptErrorsSuppressed = true;
TaskCompletionSource<bool> tcs = null;
WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) =>
tcs.TrySetResult(true);
// navigate to each URL in the list
foreach (var url in args)
{
tcs = new TaskCompletionSource<bool>();
wb.DocumentCompleted += documentCompletedHandler;
try
{
wb.Navigate(url.ToString());
// await for DocumentCompleted
await tcs.Task;
}
finally
{
wb.DocumentCompleted -= documentCompletedHandler;
}
// the DOM is ready
Console.WriteLine(url.ToString());
Console.WriteLine(wb.Document.Body.OuterHtml);
}
}
Console.WriteLine("End working.");
return null;
}
}
// a helper class to start the message loop and execute an asynchronous task
public static class MessageLoopWorker
{
public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
{
var tcs = new TaskCompletionSource<object>();
var thread = new Thread(() =>
{
EventHandler idleHandler = null;
idleHandler = async (s, e) =>
{
// handle Application.Idle just once
Application.Idle -= idleHandler;
// return to the message loop
await Task.Yield();
// and continue asynchronously
// propogate the result or exception
try
{
var result = await worker(args);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
// signal to exit the message loop
// Application.Run will exit at this point
Application.ExitThread();
};
// handle Application.Idle just once
// to make sure we're inside the message loop
// and SynchronizationContext has been correctly installed
Application.Idle += idleHandler;
Application.Run();
});
// set STA model for the new thread
thread.SetApartmentState(ApartmentState.STA);
// start the thread and await for the task
thread.Start();
try
{
return await tcs.Task;
}
finally
{
thread.Join();
}
}
}
}
From my experience in the past the webbrowser does not like operating outside of the main application thread.
Try using httpwebrequests instead, you can set them as asynchronous and create a handler for the response to know when it is succesfull:
how-to-use-httpwebrequest-net-asynchronously
A simple solution at which the simultaneous operation of several WebBrowsers occurs
Create a new Windows Forms application
Place the button named
button1
Place the text box named textBox1
Set properties of text
field: Multiline true and ScrollBars Both
Write the following
button1 click handler:
textBox1.Clear();
textBox1.AppendText(DateTime.Now.ToString() + Environment.NewLine);
int completed_count = 0;
int count = 10;
for (int i = 0; i < count; i++)
{
int tmp = i;
this.BeginInvoke(new Action(() =>
{
var wb = new WebBrowser();
wb.ScriptErrorsSuppressed = true;
wb.DocumentCompleted += (cur_sender, cur_e) =>
{
var cur_wb = cur_sender as WebBrowser;
if (cur_wb.Url == cur_e.Url)
{
textBox1.AppendText("Task " + tmp + ", navigated to " + cur_e.Url + Environment.NewLine);
completed_count++;
}
};
wb.Navigate("https://stackoverflow.com/questions/4269800/webbrowser-control-in-a-new-thread");
}
));
}
while (completed_count != count)
{
Application.DoEvents();
Thread.Sleep(10);
}
textBox1.AppendText("All completed" + Environment.NewLine);
I have a list Uri's that I want "clicked" To achieve this I"m trying to create a new web-browser control per Uri. I create a new thread per Uri. The problem I'm having is the thread end before the document is fully loaded, so I never get to make use of the DocumentComplete event. How can I overcome this?
var item = new ParameterizedThreadStart(ClicIt.Click);
var thread = new Thread(item) {Name = "ClickThread"};
thread.Start(uriItem);
public static void Click(object o)
{
var url = ((UriItem)o);
Console.WriteLine(#"Clicking: " + url.Link);
var clicker = new WebBrowser { ScriptErrorsSuppressed = true };
clicker.DocumentCompleted += BrowseComplete;
if (String.IsNullOrEmpty(url.Link)) return;
if (url.Link.Equals("about:blank")) return;
if (!url.Link.StartsWith("http://") && !url.Link.StartsWith("https://"))
url.Link = "http://" + url.Link;
clicker.Navigate(url.Link);
}
You have to create an STA thread that pumps a message loop. That's the only hospitable environment for an ActiveX component like WebBrowser. You won't get the DocumentCompleted event otherwise. Some sample code:
private void runBrowserThread(Uri url) {
var th = new Thread(() => {
var br = new WebBrowser();
br.DocumentCompleted += browser_DocumentCompleted;
br.Navigate(url);
Application.Run();
});
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
var br = sender as WebBrowser;
if (br.Url == e.Url) {
Console.WriteLine("Natigated to {0}", e.Url);
Application.ExitThread(); // Stops the thread
}
}
Here is how to organize a message loop on a non-UI thread, to run asynchronous tasks like WebBrowser automation. It uses async/await to provide the convenient linear code flow and loads a set of web pages in a loop. The code is a ready-to-run console app which is partially based on this excellent post.
Related answers:
https://stackoverflow.com/a/22262976/1768303
https://stackoverflow.com/a/21775343/1768303
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApplicationWebBrowser
{
// by Noseratio - https://stackoverflow.com/users/1768303/noseratio
class Program
{
// Entry Point of the console app
static void Main(string[] args)
{
try
{
// download each page and dump the content
var task = MessageLoopWorker.Run(DoWorkAsync,
"http://www.example.com", "http://www.example.net", "http://www.example.org");
task.Wait();
Console.WriteLine("DoWorkAsync completed.");
}
catch (Exception ex)
{
Console.WriteLine("DoWorkAsync failed: " + ex.Message);
}
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
// navigate WebBrowser to the list of urls in a loop
static async Task<object> DoWorkAsync(object[] args)
{
Console.WriteLine("Start working.");
using (var wb = new WebBrowser())
{
wb.ScriptErrorsSuppressed = true;
TaskCompletionSource<bool> tcs = null;
WebBrowserDocumentCompletedEventHandler documentCompletedHandler = (s, e) =>
tcs.TrySetResult(true);
// navigate to each URL in the list
foreach (var url in args)
{
tcs = new TaskCompletionSource<bool>();
wb.DocumentCompleted += documentCompletedHandler;
try
{
wb.Navigate(url.ToString());
// await for DocumentCompleted
await tcs.Task;
}
finally
{
wb.DocumentCompleted -= documentCompletedHandler;
}
// the DOM is ready
Console.WriteLine(url.ToString());
Console.WriteLine(wb.Document.Body.OuterHtml);
}
}
Console.WriteLine("End working.");
return null;
}
}
// a helper class to start the message loop and execute an asynchronous task
public static class MessageLoopWorker
{
public static async Task<object> Run(Func<object[], Task<object>> worker, params object[] args)
{
var tcs = new TaskCompletionSource<object>();
var thread = new Thread(() =>
{
EventHandler idleHandler = null;
idleHandler = async (s, e) =>
{
// handle Application.Idle just once
Application.Idle -= idleHandler;
// return to the message loop
await Task.Yield();
// and continue asynchronously
// propogate the result or exception
try
{
var result = await worker(args);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
// signal to exit the message loop
// Application.Run will exit at this point
Application.ExitThread();
};
// handle Application.Idle just once
// to make sure we're inside the message loop
// and SynchronizationContext has been correctly installed
Application.Idle += idleHandler;
Application.Run();
});
// set STA model for the new thread
thread.SetApartmentState(ApartmentState.STA);
// start the thread and await for the task
thread.Start();
try
{
return await tcs.Task;
}
finally
{
thread.Join();
}
}
}
}
From my experience in the past the webbrowser does not like operating outside of the main application thread.
Try using httpwebrequests instead, you can set them as asynchronous and create a handler for the response to know when it is succesfull:
how-to-use-httpwebrequest-net-asynchronously
A simple solution at which the simultaneous operation of several WebBrowsers occurs
Create a new Windows Forms application
Place the button named
button1
Place the text box named textBox1
Set properties of text
field: Multiline true and ScrollBars Both
Write the following
button1 click handler:
textBox1.Clear();
textBox1.AppendText(DateTime.Now.ToString() + Environment.NewLine);
int completed_count = 0;
int count = 10;
for (int i = 0; i < count; i++)
{
int tmp = i;
this.BeginInvoke(new Action(() =>
{
var wb = new WebBrowser();
wb.ScriptErrorsSuppressed = true;
wb.DocumentCompleted += (cur_sender, cur_e) =>
{
var cur_wb = cur_sender as WebBrowser;
if (cur_wb.Url == cur_e.Url)
{
textBox1.AppendText("Task " + tmp + ", navigated to " + cur_e.Url + Environment.NewLine);
completed_count++;
}
};
wb.Navigate("https://stackoverflow.com/questions/4269800/webbrowser-control-in-a-new-thread");
}
));
}
while (completed_count != count)
{
Application.DoEvents();
Thread.Sleep(10);
}
textBox1.AppendText("All completed" + Environment.NewLine);