User interaction in non-UI thread? - c#

in my WPF - C# application, I have a time consuming function, which I execute with a BackgroundWorker. The job of this function is to add given data from a file into a database. Now and then, I need some user feedback, for example the data is already in the store and I want to ask the user, whether he wants to merge the data or create a new object or skip the data completely. Much like the dialog windows shows, if I try to copy a file to a location, where a file with the same name already exists.
The problem is, that I cannot call a GUI-window from a non GUI-thread. How could I implement this behavior?
Thanks in advance,
Frank

You could work with EventWaitHandle ou AutoResetEvent, then whenever you want to prompt the user, you could the signal UI, and then wait for the responde. The information about the file could be stored on a variable.

If possible... my suggestion is to architect your long running task into atomic operations. Then you can create a queue of items accessible by both your background thread and UI thread.
public class WorkItem<T>
{
public T Data { get; set; }
public Func<bool> Validate { get; set; }
public Func<T, bool> Action { get; set; }
}
You can use something like this class. It uses a queue to manage the execution of your work items, and an observable collection to signal the UI:
public class TaskRunner<T>
{
private readonly Queue<WorkItem<T>> _queue;
public ObservableCollection<WorkItem<T>> NeedsAttention { get; private set; }
public bool WorkRemaining
{
get { return NeedsAttention.Count > 0 && _queue.Count > 0; }
}
public TaskRunner(IEnumerable<WorkItem<T>> items)
{
_queue = new Queue<WorkItem<T>>(items);
NeedsAttention = new ObservableCollection<WorkItem<T>>();
}
public event EventHandler WorkCompleted;
public void LongRunningTask()
{
while (WorkRemaining)
{
if (_queue.Any())
{
var workItem = _queue.Dequeue();
if (workItem.Validate())
{
workItem.Action(workItem.Data);
}
else
{
NeedsAttention.Add(workItem);
}
}
else
{
Thread.Sleep(500); // check if the queue has items every 500ms
}
}
var completedEvent = WorkCompleted;
if (completedEvent != null)
{
completedEvent(this, EventArgs.Empty);
}
}
public void Queue(WorkItem<T> item)
{
// TODO remove the item from the NeedsAttention collection
_queue.Enqueue(item);
}
}
Your UI codebehind could look something like
public class TaskRunnerPage : Page
{
private TaskRunner<XElement> _taskrunner;
public void DoWork()
{
var work = Enumerable.Empty<WorkItem<XElement>>(); // TODO create your workItems
_taskrunner = new TaskRunner<XElement>(work);
_taskrunner.NeedsAttention.CollectionChanged += OnItemNeedsAttention;
Task.Run(() => _taskrunner.LongRunningTask()); // run this on a non-UI thread
}
private void OnItemNeedsAttention(object sender, NotifyCollectionChangedEventArgs e)
{
// e.NewItems contains items that need attention.
foreach (var item in e.NewItems)
{
var workItem = (WorkItem<XElement>) item;
// do something with workItem
PromptUser();
}
}
/// <summary>
/// TODO Use this callback from your UI
/// </summary>
private void OnUserAction()
{
// TODO create a new workItem with your changed parameters
var workItem = new WorkItem<XElement>();
_taskrunner.Queue(workItem);
}
}
This code is untested! But the basic principle should work for you.

Specifically to your case
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(1000);
var a = Test1("a");
Thread.Sleep(1000);
var b = (string)Invoke(new Func<string>(() => Test2("b")));
MessageBox.Show(a + b);
}
private string Test1(string text)
{
if (this.InvokeRequired)
return (string)this.Invoke(new Func<string>(() => Test1(text)));
else
{
MessageBox.Show(text);
return "test1";
}
}
private string Test2(string text)
{
MessageBox.Show(text);
return "test2";
}
Test2 is a normal method which you have to invoke from background worker. Test1 can be called directly and uses safe pattern to invoke itself.
MessageBox.Show is similar to yourForm.ShowDialog (both are modal), you pass parameters to it (text) and you return value (can be a value of property of yourForm which is set when form is closed). I am using string, but it can be any data type obviously.

From the input of the answers here, I came to the following solution:
(Mis)Using the ReportProgress-method of the Backgroundworker in Combination with a EventWaitHandle. If I want to interact with the user, I call the ReportProgress-method and setting the background process on wait. In the Handler for the ReportProgress event I do the interaction and when finished, I release the EventWaitHandle.
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
}
// Starting the time consuming operation
private void Button_Click(object sender, RoutedEventArgs e)
{
bgw.RunWorkerAsync();
}
// using the ProgressChanged-Handler to execute the user interaction
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UserStateData usd = e.UserState as UserStateData;
// UserStateData.Message is used to see **who** called the method
if (usd.Message == "X")
{
// do the user interaction here
UserInteraction wnd = new UserInteraction();
wnd.ShowDialog();
// A global variable to carry the information and the EventWaitHandle
Controller.instance.TWS.Message = wnd.TextBox_Message.Text;
Controller.instance.TWS.Background.Set();
}
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(e.Result.ToString());
}
// our time consuming operation
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
// need 4 userinteraction: raise the ReportProgress event and Wait
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
// The WaitHandle was released, the needed information should be written to global variable
string first = Controller.instance.TWS.Message.ToString();
// ... and again
Thread.Sleep(2000);
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
e.Result = first + Controller.instance.TWS.Message;
}
I hope I did not overlooked some critical issues. I'm not so familar with multithreading - maybe there should be some lock(object) somewhere?

Related

C# backgroundWorker cancellation and invoke

I have 2 questions about backgroundWorker: one is cancellation and another is invoking.
My code briefly looks like this:
public partial class App : Form {
//Some codes omitted
public EditProcess Process = new EditProcess(ProcessTextBox);
private void ExecuteBtn_Click (object sender, EventArgs e) {
//DnldBgWorker is a backgroundWorker.
Download Dnld = new Download(dir, Process);
DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
DnldBgWorker.RunWorkerAsync();
DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
}
private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
foreach(string url in urllist) {
Dnld.Dnld(url);
}
for (int i = 0; i < 10; i++) {
System.Threading.Thread.Sleep(50);
if (DnldBgWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
}
private void StopBtn_Click(object sender, EventArgs e) {
DnldBgWorker.CancelAsync();
}
}
public class Download {
// Some codes omitted
public WebClient client = new WebClient();
public EditProcess Process;
public Download(string dir, EditProcess Process) {
this.dir = dir;
this.Process = Process;
}
public void Dnld() {
client.DownloadFile(url, dir);
EditProcess.Text(String.Format("Downloaded: {0}\r\n"));
}
}
public class EditProcess {
public TextBox Box;
public EditProcess(TextBox Box) {
this.Box = Box;
}
public void Text(string textToAdd) {
Box.Text += textToAdd;
}
}
First, while DnldBgWorker is running, I clicked StopBtn to stop the DnldBgWorker and the asynchronous work would not stop. How should I stop DnldBgWorker?
Second, EditProcess.Text(String.Format("Downloaded: {0}\r\n")); would give me an error that cross-thread operation is not valid. I know that I should make a delegate to do this, but I don't know exactly how.
++) My code looks like it's doing very simple works in very complicated way, but I put really essential elements in this code so please understand
Let's address the issue before we get into the code
For some reason, you have a completely redundant loop waiting for cancel after your actual download is done. Hence BtnStop does not work for you
When you call EditProcess.Text from Dnld which is invoked in the BackgroundWorker context, you are accessing a GUI element from a thread which does not "own" it. You can read in detail about cross-thread operation here. In your case, you should do it via your ReportProgress call.
Now you can see how I have
Removed the redundant loop from GoDownload while moving the if (DnldBgWorker.CancellationPending) check to the download loop. This should make the StopBtn work now.
Added the ProgressChanged event handler to do the GUI change in the ExecuteBtn_Click. This is triggered by DnldBgWorker.ReportProgress call in the download loop of GoDownload method. Here we pass the custom formatted string as UserState
Also make sure that you have the enabled the ReportsProgress and SupportsCancellation properties like below, perhaps in your designer property box or in code lile DnldBgWorker.WorkerReportsProgress = true; DnldBgWorker.WorkerSupportsCancellation = true;
Hope everything else is clear with the code below.
public partial class App : Form {
//Some codes omitted
public EditProcess Process = new EditProcess(ProcessTextBox);
private void ExecuteBtn_Click (object sender, EventArgs e) {
//DnldBgWorker is a backgroundWorker.
Download Dnld = new Download(dir, Process);
DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
DnldBgWorker.RunWorkerAsync();
DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
DnldBgWorker.ProgressChanged += (s, e) => EditProcess.Text((string)e.UserState);;
}
private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
foreach(string url in urllist) {
Dnld.Dnld(url);
DnldBgWorker.ReportProgress(0, String.Format($"Downloaded: {url}\r\n"));
if (DnldBgWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
}
private void StopBtn_Click(object sender, EventArgs e) {
DnldBgWorker.CancelAsync();
}
}
public class Download {
// Some codes omitted
public WebClient client = new WebClient();
public EditProcess Process;
public Download(string dir, EditProcess Process) {
this.dir = dir;
this.Process = Process;
}
public void Dnld() {
client.DownloadFile(url, dir);
}
}
public class EditProcess {
public TextBox Box;
public EditProcess(TextBox Box) {
this.Box = Box;
}
public void Text(string textToAdd) {
Box.Text += textToAdd;
}
}
There are 2 issues here:
Regarding cancellation - you need to check for cancellation status in the loop that does downloading (thus downloading only part of requested files), not in the later loop which I don't really understand.
As an additional side note you can avoid using BackgroundWorker by using WebClient.DownloadFileAsync and WebClient.CancelAsync combo.
As of reporting progress - make you BackgroundWorker report progress back to the UI thread via ReportProgress and update UI from there.
As for how to cancel a thread. Here is a basic example, for a console application, that I hope you can fit into your more complex code.
void Main()
{
var tokenSource = new CancellationTokenSource();
System.Threading.Tasks.Task.Run(() => BackgroundThread(tokenSource.Token));
Thread.Sleep(5000);
tokenSource.Cancel();
}
private void BackgroundThread(CancellationToken token)
{
while (token.IsCancellationRequested == false) {
Console.Write(".");
Thread.Sleep(1000);
}
Console.WriteLine("\nCancellation Requested Thread Exiting...");
}
The results would be the following.
.....
Cancellation Requested Thread Exiting...
Secondly, as far as how to invoke from your thread to interact with the user interface, hopefully this blog will help you. Updating Windows Form UI elements from another thread
Please let me know if you found this helpful.
To support cancellation you need to set the property
DnldBgWorker.WorkerSupportsCancellation = true;
It is not clear if you set it somewhere else, but you need it to cancel the background worker as you can read on MSDN
Set the WorkerSupportsCancellation property to true if you want the
BackgroundWorker to support cancellation. When this property is true,
you can call the CancelAsync method to interrupt a background
operation.
Also I would change the GoDownload method to
private void GoDownload(Download Dnld, string[] urllist, EventArgs e)
{
foreach(string url in urllist)
{
Dnld.Dnld(url);
// this is just to give more time to test the cancellation
System.Threading.Thread.Sleep(500);
// Check the cancellation after each download
if (DnldBgWorker.CancellationPending)
{
e.Cancel = true;
return;
}
}
}
For the second problem you need to call that method when your code is running on the UI thread and not in the background thread. You could easily achieve this moving the textbox update in the event handler for the ProgressChanged event. To set up the event handler you need another property set to true
DnldBgWorker.WorkerReportsProgress = true;
And set the event handler for the ProgressChanged event
DnldBgWorker.ProgressChanged += DnldBgWorker_ProgressChanged;
private void DnldBgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
EditProcess.Text(String.Format("Downloaded: {0}\r\n", e.ProgressPercentage));
}
and raise this event in the GoDownload with
DnldBgWorker.ReportProgress(i);

How can I create a collection of items that

Let me try to explain the design pattern I'm looking to implement. I have a Queue<Item> or some other collection of Items that needs to be processed by some method
static void Process(Item item)
{
// ...
}
They can be processed synchronously or asynchronously.
This queue will be getting items added to it periodically, because I have some method like
static void AddWorkToQueue()
{
// ...
}
that gets run on a timer
timer.Elapsed += AddWorkToQueue();
So what I need is some type "continuous while loop" that stops whenever the queue is empty.
How can I use C#.NET to do this in the best way? Are there any built-in libraries for solving this type of problem?
You could create a custom queue class and change it's listener:
public class CustomQueue : Queue<object>
{
public event EventHandler FirstItemInserted;
protected virtual void OnFirstItemInserted()
{
FirstItemInserted?.Invoke(this, EventArgs.Empty);
}
//Modified Enqueue method.
public new void Enqueue(object obj)
{
//Call the listener every time an item is inserted into the empty queue.
if (Count == 0)
{
base.Enqueue(obj);
OnFirstItemInserted();
}
else
base.Enqueue(obj);
}
}
The "Enqueue" method was changed to call the listener after each first insert.
All you'll need to do is to call a method to consume each item of your queue as long as there are items to dequeue.
class MyProgram
{
private static CustomQueue MyQueue;
public MyProgram()
{
MyQueue = new CustomQueue();
MyQueue.FirstItemInserted += ConsumeQueue;
//Activate timer...
}
private static void ConsumeQueue(object sender, EventArgs e)
{
object item;
while (MyQueue.Count > 0)
{
item = MyQueue.Dequeue();
//Do something...
}
}
}
You can use BufferBlock<Item> from TPL DataFlow (https://msdn.microsoft.com/en-us/library/hh160414(v=vs.110).aspx):
using System.Threading.Tasks.Dataflow;
static void AddWorkToQueue()
{
queue.Send(new Item());
}
static async void MainLoop()
{
while (true) // may be you need some cancellation token to trigger end of processing
{
var item = await queue.ReceiveAsync();
ProcessItem(item);
}
}

Handling events after receiving a MSMQ message (thread issue?)

I created two separate Windows Forms applications in C# that use MSMQ for communicating. Here's how it works, it looked simple enough though:
App1 sends a details request to App2.
App2 creates an event to open the window.
App2 opens a "details" window.
The only problem I have is that when received the message, the "details" window freezes after appearing.
As I handle MSMQ messages handling in an object that uses threads, I suspect the problem comes from there... But I have no experience in handling MSMQ messages or specific events handling between parts of an application.
Here's part of the code I use for App2:
/*Class declared in the Core namespace*/
public class TaskMessageQueueHandler
{
public TaskMessageQueueHandler()
{
this.Start();
}
private Thread m_thread;
private ManualResetEvent m_signal;
public event System.EventHandler messageReceived;
public void Start()
{
m_signal = new ManualResetEvent(false);
m_thread = new Thread(MSMQReceiveLoop);
m_thread.Start();
}
public void Stop()
{
m_signal.Set();
}
protected virtual void SendEvent(object sender, EventArgs e)
{
if (messageReceived != null)
messageReceived(this.message, e);
}
public string message;
private void MSMQReceiveLoop()
{
bool running = true;
MessageQueue queue = new MessageQueue(#".\Private$\queue1");
while (running)
{
try
{
var message = queue.Receive();
message.Formatter = new XmlMessageFormatter(new String[] { "System.String,mscorlib" });
this.message = message.Body.ToString();
string m = this.message;
SendEvent(m, System.EventArgs.Empty);
if (m_signal.WaitOne(10))
{
running = false;
}
}
catch
{
Console.WriteLine("ERROR");
running = false;
}
}
}
}
/*Main process, in the Program namespace*/
[...]
Core.TaskMessageQueueHandler tmqh = new Core.TaskMessageQueueHandler();
EventListener el = new EventListener();
tmqh.messageReceived += new System.EventHandler(el.ShowDetails);
[...]
/* Class in the Program namespace */
class EventListener
{
public void ShowDetails(object sender, EventArgs e)
{
int numero = int.Parse(sender as string);
Details details = new Details(numero);
details.Show();
}
}
Where did I go wrong? Where did I go right?
Thanks a lot,
Stephane.P
EDIT: if the MSMQ handler is stopped with Stop() anywhere around the event sending, the details window appears then disappears right away...
EDIT2: After the workaround given by Slugart, I managed to make this work:
class EventListener
{
Main control;
public EventListener(Main main)
{
control = main;
}
public void ShowDetails(object sender, EventArgs e)
{
int numero = int.Parse(sender as string);
control.Invoke((Action)(() => ShowDetails(numero)));
}
private void ShowDetails(int numero)
{
Details details = new Details(numero);
details.Show();
}
}
Which is used like:
Core.TaskMessageQueueHandler tmqh = new Core.TaskMessageQueueHandler();
EventListener el = new EventListener(this);
tmqh.messageReceived += new System.EventHandler(el.ShowDetails);
You're creating and displaying a form Details on a thread other than the main GUI thread and not an STA thread at that.
Your EventListener should have a reference to a running form (your main form perhaps) and then call form.Invoke() on it.
class EventListener
{
Control control; // A valid running winforms control/form created on an STA thread.
public void ShowDetails(object sender, string message)
{
int numero = int.Parse(message);
control.Invoke(() => ShowDetails(numero))
}
private void ShowDetails(int numero)
{
Details details = new Details(numero);
details.Show();
}
}
Also sending your event data as the sender is not really following the Event pattern that has been put in front of you. You want to use the EventArgs parameter for this, use the EventHandler delegate (EventHandler in your case).

Progress bar working but label is not updating

I have a library with an Interface.
Public Interface Progress
{
int ProgressValue{get;set;},
string ProgressText{get;set;},
}
Library has a method Create (dummy code):
Public Class TestLibrary
{
Progress _progress;
Public void Create()
{
foreach(var n in TestList)
{
// Do Something
_progress.ProgressValue = GetIndex(n);
_progress.ProgressText = "Updating..." + n;
}
}
}
I have a project that references this library and calls Create method. It even Implements Interface Progress.
Public Class TestProject : Progress
{
public int ProgressValue
{
get{return progressBar1.Value;}
set{progressBar1.Value = value;}
}
public int ProgressText
{
get{return label1.Text;}
set{label1.Text = value;}
}
}
Now when I run the application, Progress Bar behaves properly and shows the progress correctly, but the Text of label1 does not change at all. But it do change in the end of for loop and shows the last item in loop. Can anyone help me out in this?
Note: All these codes are written directly without testing as I don't have my application now with me. Sorry for any syntax errors.
It sounds like all your work is being done on the UI thread. Don't do that.
Instead, run the loop itself in a background thread, and use Control.Invoke or Control.BeginInvoke (probably in the Progress implementation) to marshal a call across to the UI thread just to update the UI. This will leave your UI responsive (and able to update labels etc) while it's still processing.
Used a Label instead of ProgressBar. You can try this code [using BackGroundWorker] -
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
private BackgroundWorker _worker;
BusinessClass _biz = new BusinessClass();
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
_worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
int junk = 0;
for (i = 0; i <= 199990000; i++)
{
int result = _biz.MyFunction(junk);
junk++;
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// note I can pass the business class result also and display the same in the LABEL
_worker.ReportProgress(percentComplete, result);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
}
else if (e.Error != null)
{
// Do something with the error
}
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
}
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
}
Posted the same a few days ago here
The code you posted uses one thread. That means every operation is excuted in sequence right after the previous one finished.
Since you can update GUI elements right away, I suppose the code to be run from main thread (a.k.a "GUI thread"). Blocking the GUI thread results in the GUI ("Graphical User Interface") not updating until there is some idle time for it.
Use following example to refresh your label every iteration so that it updates your UI.
label1.Text = i.ToString();
label1.Refresh();

Call Method B if method A is not called for more than N seconds

I'm using following code to call Method B after N seconds method A is called. If method A
is called again within the N seconds timeout, i have to reset the time counting back to N seconds.
I cannot reference System.Windows.Form in my project, so I cannot use System.Windows.Form.Timer.
The method B must be called in the same thread A is called.
private void InitTimer()
{
timer = new BackgroundWorker();
timer.WorkerSupportsCancellation = true;
timer.WorkerReportsProgress = true;
timer.DoWork += delegate(object sender, DoWorkEventArgs e)
{
var st = DateTime.Now;
while (DateTime.Now.Subtract(st).TotalSeconds < 10)
{
if (timer.CancellationPending)
{
e.Cancel = true;
return;
}
}
};
timer.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e)
{
if (!e.Cancelled)
{
MethodB();
}
else
{
timer.RunWorkerAsync();
}
};
}
public void MethodA()
{
if (timer.IsBusy)
timer.CancelAsync();
else
timer.RunWorkerAsync();
}
public void MethodB()
{
//do some stuff
}
Actually the code work, but i think it's a bit confounding. Do you know if there is a best practices to achieve the same result?
It's a shame you're stuck on .NET 2.0, because Rx extensions has a Throttle method that achieves this effect quite elegantly.
Sadly Rx requires at least .NET 3.5 SP1.
Oh well! You can always use a System.Threading.Timer to get this done instead. Synchronization can be provided by leveraging the current SynchronizationContext (this is what BackgroundWorker does).
Here's a sketch of a LaggedMethodPair class to illustrate this approach. The class takes three inputs in its constructor: an Action to be performed on-demand, another Action to serve as the callback that will be invoked when a given timeout has elapsed, and, of course, the timeout itself:
public sealed class LaggedMethodPair
{
private SynchronizationContext _context;
private Timer _timer;
private Action _primaryAction;
private Action _laggedCallback;
private int _millisecondsLag;
public LaggedMethodPair(Action primaryAction,
Action laggedCallback,
int millisecondsLag)
{
if (millisecondsLag < 0)
{
throw new ArgumentOutOfRangeException("Lag cannot be negative.");
}
// Do nothing by default.
_primaryAction = primaryAction ?? new Action(() => { });
// Do nothing by default.
_laggedCallback = laggedCallback ?? new Action(() => { });
_millisecondsLag = millisecondsLag;
_timer = new Timer(state => RunTimer());
}
public void Invoke()
{
// Technically there is a race condition here.
// It could be addressed, but in practice it will
// generally not matter as long as Invoke is always
// being called from the same SynchronizationContext.
if (SynchronizationContext.Current == null)
{
SynchronizationContext.SetSynchronizationContext(
new SynchronizationContext()
);
}
_context = SynchronizationContext.Current;
ResetTimer();
_primaryAction();
}
void ResetTimer()
{
_timer.Change(_millisecondsLag, Timeout.Infinite);
}
void RunTimer()
{
_context.Post(state => _laggedCallback(), null);
}
}
I wrote a sample Windows Forms app to show this class in action. The form contains a LaggedMethodPair member with a timeout of 2000 ms. Its primaryAction adds an item to a list view. Its laggedCallback adds a highlighted item to the list view.
You can see that the code runs as expected.
I would encapsulate this functionality into a timer class with events that other classes can subscribe to (for example a timer.tick event).
I am trying to use AutoResetEvent, because it is capable to wait for a signal. I use it to have worker waited for the signal from A(), and if it has been too long B() will be called.
class Caller
{
AutoResetEvent ev = new AutoResetEvent(false);
public void A()
{
ev.Set();
// do your stuff
Console.Out.WriteLine("A---");
}
void B()
{
Console.Out.WriteLine("B---");
}
public void Start()
{
var checker = new BackgroundWorker();
checker.DoWork += new DoWorkEventHandler(checker_DoWork);
checker.RunWorkerAsync();
}
void checker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (!worker.CancellationPending)
{
bool called = ev.WaitOne(TimeSpan.FromSeconds(3));
if (!called) B();
}
}
}
I have tested my class roughly and it is working fine so far. Note that B will be called from worker thread, so you have to do the synchronization in B() if needed.

Categories

Resources