sharing variables between thread and class - c#

I have created an app with a class containing some data that are updated at a regular interval using a timer. This class also contains some public function to pass the data to another class. Here is how it looks:
private void OnTimer(object sender, ElapsedEventArgs status)
{
GetNewData();
}
public void GetData(ref List<double> data, int index)
{
if (index < m_data.Length)
{
data = new List<double>(m_data[index]);
}
else
{
data = new List<double>();
}
}
At the moment, I don't have any protection to assure that the function GetData is not accessing the data while it is been modified.
Could you point me to the best way of protecting my shared data?

You can put a lock on your code. Each thread will wait on your lock statement and then will go inside your new data creation:
private void OnTimer(object sender, ElapsedEventArgs status)
{
GetNewData();
}
object lockCheck = new object();
public void GetData(ref List<double> data, int index)
{
lock(lockCheck)
{
if (index < m_data.Length)
{
data = new List<double>(m_data[index]);
}
else
{
data = new List<double>();
}
}
}

You're not too specific about how you're updating your data. If your GetNewData method completely replaces the m_data member, then there's no need for a lock at all. Consider this:
// this is the shared data
SomeDataType m_data;
// Method that reads the data
public void GetData(ref List<double> data, int index)
{
// get a reference to the existing data
var localData = m_data;
// only work with the localData reference here
}
private void OnTimer(object sender, ElapsedEventArgs status)
{
GetNewData();
}
Now, make your GetNewData method load the data using a local variable. Once it's all loaded, replace the m_data reference:
private void GetNewData()
{
var localData = /* create the data here */
// replace the reference
m_data = localData;
}
Using this technique, there's no way for the get method and the timer to interfere with each other. If GetNewData is called while the getter is running, there's no problem because the getter is working with a local reference.
The only potential problem is that if the getter is called while GetNewData is running, then the value returned will be from the old list. That is, potentially stale data. Whether this is a problem is really a matter for you to decide.

There are collections specifically designed to be used for multiple threads to provide data to each other. You can use a BlockingCollection, which is a wrapper for a ConcurrentQueue to allow your timer to generate data, add it to the queue, and then have code elsewhere reading from that queue and processing the results. The BlockingCollection will then be responsible for all synchronization between threads.

Related

How to create a timer callback that can edit its state object?

I have a System.Threading.Timer that receives some data each second, and I want its callback to put the received data into an object, which should be returned to client code. I tried to implement that using C#'s reference types mechanism. Here's the current callback:
private void Receive(object? obj) {
var data = GetData();
obj = (object)data; // Does nothing
}
And here's the method that starts the timer (timer is a field):
public void Start(Image image) {
timer = new Timer(
Receive,
image,
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1)
);
}
Expected behavior: each second the data (which is image data) is received and put to the image, the reference to which is specified in parameter image.
Actual behavior: when I defined a variable of type Image, passed it to the Start method and checked its value (using the Visual Studio debugger) after 5 seconds, it was the empty Bitmap I assigned to it at the very start.
I have also checked that the data (containing non-empty image) is sent properly.
Can anybody tell me what's the issue, or, alternatively, suggest a different way to change the "state" object in a timer callback?
obj = (object)data; only sets the data locally in the Receive method. You could fix it by creating a wrapper object
public class ImageData
{
public Image Image { get; set; }
}
Then create a static instance of it that can be accessed from where you need it
public static readonly ImageData Data = new ImageData();
Depending on where you must access the images it can also an instance member and be private; however, it must always be the same instance that you used to start the timer.
Then start like this
public void Start() {
timer = new Timer(
Receive,
Data,
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1)
);
}
And receive
private void Receive(object? obj) {
var data = (ImageData)obj;
data.Image = GetData();
}
Now, you can access the new images through the static Data.Image property.
Alternatively, you could "consume" the image (i.e., display or whatever you are doing with it) directly in the Receive callback and ignore the obj parameter. However, I do not have enough context information (where do the different code pieces reside and in which thread are they executed, etc.).
Update
The Image will be null in ImageData first, until the Receiver callback is called for the first time.
If you want an image at the very start, you can do several things.
Start the timer with the first time argument as 0 (this is the time it waits until firing the first time).
Call the callback directly in Start: Receive(Data).
Assign an image with Data.Image = GetData();.
Initialize the image in ImageData with a dummy image (maybe some kind of wait image).
Update 2
Apparently you are creating a library. I assume the library will pull images regularly and make them available to a consumer.
I suggest exposing an event in the library any consumer can subscribe to to receive images.
In the library we declare event arguments as
public class UpdateImageEventArgs : EventArgs
{
public UpdateImageEventArgs(Image image)
{
Image = image;
}
public Image Image { get; }
}
A possible implementation of the library:
public class Library : IDisposable
{
public event EventHandler<UpdateImageEventArgs>? ImageUpdated;
private System.Threading.Timer? _timer;
public void Start()
{
_timer = new System.Threading.Timer(Receive, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
}
private void Receive(object? obj)
{
var image = GetImage();
ImageUpdated?.Invoke(this, new UpdateImageEventArgs(image));
}
private static Image GetImage()
{
// TODO: Add your implementation
throw new NotImplementedException();
}
public void Dispose()
{
if (_timer != null) {
_timer.Dispose();
_timer = null;
}
}
}
The consumer code:
public static void Test()
{
var library = new Library();
library.ImageUpdated += Library_ImageUpdated;
library.Start();
}
private static void Library_ImageUpdated(object? sender, UpdateImageEventArgs e)
{
DoSomethingWith(e.Image);
}
If you want to stop the timer, you can do so by calling library.Dispose();. This also ensures that the timer resources are disposed properly.
In the Recieve method you get an obj as input reference. Then you try to set a value to obj. This last step cannot work, since you work on the reference.
The only way it could work is, if the input reference is given as a ref, which is
private void Receive(ref object? obj) {}
Passing a reference type by reference enables the called method to replace the object to which the reference parameter refers in the caller.

Preventing GUI hangs in WPF

I am relatively new to C# WPF projects and I have an issue with my code whereby the user interface "locks up" whilst I am running a task. Here is the relevant code:
public partial class MainWindow : Window {
// handles to ManagedDLAContainer objects
private ManagedDLA2DContainer dla_2d;
public MainWindow() {
InitializeComponent();
// initalise aggregate containers
dla_2d = new ManagedDLA2DContainer();
}
private void GenerateAggregate() {
// generate the 2D aggregate of size given by particles_slider value
Dispatcher.Invoke(new Action(() => { dla_2d.Generate((uint)particles_slider.Value); }));
// TODO: add particles to "canvas" on GUI as they are generated
}
private void GenerateButtonClick(object sender, RoutedEventArgs e) {
// set the coefficient of stickiness of aggregate
// to current value of stickiness_slider
dla_2d.SetCoeffStick(stickiness_slider.Value);
// start asynchronous task calling GenerateAggregate method
Task.Factory.StartNew(() => GenerateAggregate());
}
}
Here ManagedDLA2DContainer is a managed C++/CLI wrapper for some native, unmanaged C++ code and stickiness_slider is simply a Slider element of the WPF interface; whilst, similarly, particles_slider is another Slider element of the interface. I should note that the following code also results in the GUI hanging:
public partial class MainWindow : Window {
// lock object for multi-threading tasks
private static readonly object locker = new object();
// handles to ManagedDLAContainer objects
private ManagedDLA2DContainer dla_2d;
public MainWindow() {
InitializeComponent();
// initalise aggregate containers
dla_2d = new ManagedDLA2DContainer();
}
private void GenerateAggregate() {
// lock around aggregate generation
lock (locker) {
// generate the 2D aggregate of size given by particles_slider value
Dispatcher.Invoke(new Action(() => { dla_2d.Generate((uint)particles_slider.Value); }));
// TODO: add particles to "canvas" on GUI as they are generated
}
}
private void GenerateButtonClick(object sender, RoutedEventArgs e) {
// set the coefficient of stickiness of aggregate
// to current value of stickiness_slider
dla_2d.SetCoeffStick(stickiness_slider.Value);
Thread agg_gen_thread = new Thread(GenerateAggregate);
agg_gen_thread.Start();
agg_gen_thread.Join();
}
}
Any information which could help me understand what I might be doing wrong here is appreciated.
Also, if you are curious then all the code for this project is available here: https://github.com/SJR276/DLAProject
You start new thread in which you run GenerateAggregate. Then immediatly you dispatch all work back to user interface thread via Dispatcher.Invoke. So basically you are running long running task on UI thread and it blocks.
Instead, you should dispatch to UI thread only operations that require that (which update UI controls for example), not the whole operation.
Suppose your Generate function looks like this:
void Generate() {
MakeCpuIntensiveWork();
UpdateUIWithResults();
}
You only need to dispatch second part to UI thread. In your case, as we found out in comments, the only UI part is getting value of a slider. So you can split like this:
private void GenerateAggregate()
{
uint sliderValue = 0;
// generate the 2D aggregate of size given by particles_slider value
Dispatcher.Invoke(() => { sliderValue = (uint)particles_slider.Value; });
dla_2d.Generate(sliderValue);
// TODO: add particles to "canvas" on GUI as they are generated
}

UI not updating when TableAdapter.Fill is called from another Thread

I'm developing an MDI application in C# with .NET 4.0.
Each MDI child will be a form with tabs that contains GroupBoxes with a DataGridView.
I implemented a class that is used to manage Threads.
This is the StartNewThread method in my ThreadManager class
public string StartNewThread(ThreadStart threadMethod, string threadName)
{
try
{
Thread thread = new Thread(() => threadMethod());
thread.Name = threadName + " (" + _threadCount++.ToString("D4") + ")";
thread.Start();
_threadList.Add(thread.Name, thread);
return thread.Name;
}
catch (Exception ex)
{
//Log and manage exceptions
}
return null;
}
To create the DataGridViews I used some Wizard component from Oracle Developer Tools for VS library. So, after creating the DataSource and so the DataSet, then I used drag&drop from DataSource tree to drag tables and automatically create DataGridViews.
This is the actual working code, behind the child form, automatically created.
public partial class ScuoleNauticheForm : Form
{
public ScuoleNauticheForm()
{
InitializeComponent();
}
private void ScuoleNauticheForm_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.PERSONALE' table. You can move, or remove it, as needed.
this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
// TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.NATANTI' table. You can move, or remove it, as needed.
this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
// TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.SCUOLE' table. You can move, or remove it, as needed.
this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
}
}
What I want to do now is manage all the load/query/insert/update/delete operations on separated threads. For now I tried to create a new Thread to load the data.
This i what I tried.
public partial class ScuoleNauticheForm : Form
{
private readonly ThreadManager _threadManager;
public ScuoleNauticheForm()
{
InitializeComponent();
_threadManager = ThreadManager.GetInstance();
}
private void ScuoleNauticheForm_Load(object sender, EventArgs e)
{
_threadManager.StartNewThread(LoadData, "LoadData");
}
#region DataBind
private void LoadData()
{
// TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.PERSONALE' table. You can move, or remove it, as needed.
this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
// TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.NATANTI' table. You can move, or remove it, as needed.
this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
// TODO: This line of code loads data into the 'dEVRAC_NauticheDataSet.SCUOLE' table. You can move, or remove it, as needed.
this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
}
#endregion
}
It works only for half... There's no errors or exceptions, but if I load data that way, using a different Thread, the DataGridviews doesn't update and I don't see any data when opening the form, even if I move or resize it. Otherwise, using the automatically generated code, the DataGridViews are populated correctly.
But, since the wizard also add a navigation bar to the form to navigate through records, I noticed that it works, because it counts the correct number of records and I can use the arrows (first, previous, next, last) to move across records.
Here is an image showing my form.
See the navigation bar that is showing the correct number of total records (14) and allows me to navigate through them.
Do I need to use delegates? If so, I think it would be a mess... how many delegates should I create and for those methods? Or is there another solution?
-- UPDATE 1 --
I know that UI threads are automatically managed by .NET and so the programmer don't need to manage them with code. So, should it be a problem of synchronization with the .NET UI thread built in management? Maybe my thread launched by Form.Load() interferes with the UI thread managed by the .NET?
-- UPDATE 2 --
I tried to implement the solution proposed by faby. I replaced my Thread logic with Task logic. The behaviour of the application is the same, so everything that was working with Thread is now working also with Task. But the problem still remains. Since I'm on .NET 4.0 and not .NET 4.5, I could not use async and await. So I don't know if with that approach the UI will work correctly or not.
Any other suggestion valid for .NET 4.0?
do you consider the option of BackgroundWorker Class ?
implementing DoWork and ProgressChanged you can do in DoWork what you are doing in background thread and in ProgressChanged you can update the UI
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//long running task
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//update the UI components
}
update 1
another solution could be something like this
public Task LoadDataAsync()
{
return Task.Factory.StartNew( () =>
{
//code to fill your datagridview
});
}
then
public async Task ChangeUIComponents()
{
await LoadDataAsync();
// now here you can refresh your UI elements
}
update 2
to use async/await with framework 4.0 try with this NugetPackage (Microsoft.Bcl.Async)
I finally found a solution without using async/await and other libraries.
The problem was that I was executing the Fill() method of TableAdapter inside a new Task and so I needed to use InvokeRequired to set the binding source data source to the DataTable within the right thread.
So I used delegates. I changed the method called on the new Task and make it call 3 other methods (one for each DataGridView to fill) that call Fill() implementing the InvokeRequired check.
Now I see the creation of the UI and then, after a couple of seconds, the asynchronous filling of the DataGridViews.
This article was useful: Load data from TableAdapter async
Thanks to #faby for the suggestion to use Task instead of Thread. It was not the solution but it is a better way to do Threading.
Here's the final working code.
public partial class ScuoleNauticheForm : Form
{
private readonly TaskManager _taskManager;
public ScuoleNauticheForm()
{
InitializeComponent();
_taskManager = TaskManager.GetInstance();
}
private void ScuoleNauticheForm_Load(object sender, EventArgs e)
{
_taskManager.StartNewTask(LoadData);
}
#region Delegates
public delegate void FillPersonaleCallBack();
public delegate void FillNatantiCallBack();
public delegate void FillScuoleCallBack();
#endregion
#region DataBind
private void LoadData()
{
FillPersonale();
FillNatanti();
FillScuole();
}
public void FillPersonale()
{
if (PersonaleDataGridView.InvokeRequired)
{
FillPersonaleCallBack d = new FillPersonaleCallBack(FillPersonale);
Invoke(d);
}
else
{
this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
}
}
public void FillNatanti()
{
if (NatantiDataGridView.InvokeRequired)
{
FillNatantiCallBack d = new FillNatantiCallBack(FillNatanti);
Invoke(d);
}
else
{
this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
}
}
public void FillScuole()
{
if (ScuoleDataGridView.InvokeRequired)
{
FillScuoleCallBack d = new FillScuoleCallBack(FillScuole);
Invoke(d);
}
else
{
this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
}
}
#endregion
}
-- Update 1 --
If the methods to call by the new Task are void and without any parameters, you can simplify a bit the above code by using Invoke((MethodInvoker) MethodName). The behaviour of the application is the same.
Here's the simplified version of the code.
public partial class ScuoleNauticheForm : Form
{
private readonly TaskManager _taskManager;
public ScuoleNauticheForm()
{
InitializeComponent();
_taskManager = TaskManager.GetInstance();
}
private void ScuoleNauticheForm_Load(object sender, EventArgs e)
{
_taskManager.StartNewTask(LoadData);
}
#region DataBind
private void LoadData()
{
// Since Fill Methods are void and without parameters,
// you can use the Invoke method without the need to specify delegates.
Invoke((MethodInvoker)FillPersonale);
Invoke((MethodInvoker)FillNatanti);
Invoke((MethodInvoker)FillScuole);
}
public void FillPersonale()
{
this.PersonaleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.PERSONALE);
}
public void FillNatanti()
{
this.NatantiTableAdapter.Fill(this.DEVRAC_NauticheDataSet.NATANTI);
}
public void FillScuole()
{
this.ScuoleTableAdapter.Fill(this.DEVRAC_NauticheDataSet.SCUOLE);
}
#endregion
}

Thread contained inside class

I'm writing a simple Windows forms application to get me into the swing of things with Threads. So far what I have is working, but what I would like to do is contain it all in a seperate class rather than directly in my forms code.
I have a background thread that starts and retrieves data from a database. I then display that data in to a listbox.
private delegate void UpdateListValues(List<ListBoxItem> itemList);
private void form_main_Shown(object sender, EventArgs e)
{
// Set the loading text.
list_selection.Items.Add(ListHelpers.LoadingItem());
// Start the data access on a seperate thread.
Thread worker = new Thread(GetInvoicingData);
worker.IsBackground = true;
worker.Start();
}
private void GetInvoicingData()
{
// Query database
List<ListBoxItem> values = DAC.GetInvoicingAccounts();
// Display results
BeginInvoke(new UpdateListValues(DisplayList), new object[] { values });
}
private void DisplayList(List<ListBoxItem> itemList)
{
// Display each result
list_selection.Items.Clear();
for (int i = 0; i < itemList.Count; i++)
{
list_selection.Items.Add(itemList[i]);
}
}
The problem is that in the DisplayList method, I won't be able to access the list box (list_selection) because it's part of the form class. Does anyone have any suggestions on how I can do this.
Also, I'm new to threading so feel free to tell me I'm doing it absolutely wrong. I just used the example from http://www.codeproject.com/Articles/23517/How-to-Properly-Handle-Cross-thread-Events-and-Upd to get me to where I am now.
Thanks
How about something like this:
// Added the form's class declaration to highlight separation of thread code into a separate class, but may not be exactly the same as yours depending on naming
public class Form1 : Form
{
private readonly DataRetriever _dataRetriever;
private void form_main_Shown(object sender, EventArgs e)
{
// Set the loading text.
list_selection.Items.Add(ListHelpers.LoadingItem());
// Create the DataRetriever, and provide it with a delegate to DisplayList for returning data
_dataRetriever = new DataRetriever(DisplayList);
// Start retrieving data on a separate thread...
_dataRetriever.GetData();
}
private void DisplayList(List<ListBoxItem> itemList)
{
if (InvokeRequired)
{
// Ensure the update occurs on the UI thread
Invoke((Action)(() => DisplayList(itemList)));
return;
}
// Display each result
list_selection.Items.Clear();
foreach (var item in itemList)
{
list_selection.Items.Add(item);
}
}
}
// Separate class to hold thread code
public class DataRetriever
{
public delegate void UpdateCallbackDelegate(List<ListBoxItem> itemList);
private readonly UpdateCallbackDelegate _updateCallback;
public DataRetriever(UpdateCallbackDelegate updateCallback)
{
_updateCallback = updateCallback;
}
public void GetData()
{
var thread = new Thread(GetInvoicingData)
{
IsBackground = true
};
thread.Start();
}
private void GetInvoicingData()
{
// Not sure whether "DAC" is a static class, if it needs to be constructed
// in the DataRetriever's constructor, or passed to it as a parameter
_updateCallback(DAC.GetInvoicingAccounts());
}
}
As you can see, all the thread code is now in a separate class DataRetriever, and a delegate provided when constructing it to enable the retrieved data to be passed back to the form once the retrieval is complete. The method that handles the callback ensures that the call is marshalled to the UI thread to prevent cross-thread exceptions.
I would like to point out that this is not presented as the "best" way to do this, but merely as an answer to the question (how to separating threading code into a separate class). As others have mentioned, there are already mechanisms in place to do this sort of thing (e.g. BackgroundWorker). Some complexity has been omitted for clarity. For example, in the implementation presented here, if you were to call GetData() multiple times (with each call occurring before the previous ones have returned their data), you would have multiple queries occurring simultaneously, and as they are running asynchronously, may return their data in an arbitrary order. This may or may not be an issue in your case.

Timer within Thread within Windows Service

I cant figure out how to proceed the best way with this problem.
Right now I have a windows service which only task is to gather data from a database with a specific DSN and then send out an email if the data is valid. The service contains a timer which ticks every 5 minuts and performs the tasks above.
Now I need to re-write the windows service to be able to run on more than 1 DSN.
I was thinking of making several threads inside the windows service and then again have a seperat timer inside each thread.
Is this a good idea and how can this be done? I want to avoid having a windows service for each DSN.
Ill try to draw it if I dont make any sense
Windows Service
Thread1(DSN1)-----------------------------Thread2(DSN2)----------------------Thread3(DSN3)
Timer(ticks every X minuts)-----------------Timer(same)-------------------------Timer(same)
Logic()---------------------------------------------Logic---------------------------------Logic()
Hope my problem makes sense :)
As far as I Know each timer represents a thread on its own. Knowing this, I would try to dynamically create timer objects for each given dsn.
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
private List<GetDataFromDSN> list = null;
protected override void OnStart(string[] args)
{
list = new List<GetDataFromDSN>();
// assume args contains your given dsn values
foreach (string dsn in args)
{
GetDataFromDSN newObj = new GetDataFromDSN();
newObj.DSN = dsn;
list.Add(newObj);
newObj.Start();
}
}
}
public class GetDataFromDSN
{
public string DSN { get; set; }
private Timer timer = null;
private double interval = 1000*60*5; // 5 minutes interval
public GetDataFromDSN()
{
// init your object
timer = new Timer(interval);
timer.Elapsed +=new ElapsedEventHandler(timer_Elapsed);
}
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
// do what ever you want
}
public void Start() // or even make timer public
{
timer.Start();
}
public void Stop()
{
timer.Stop();
}
}
Do each of the DSNs need to be on a separate Thread?
If you were to encapsulate the Email retrieval and validation logic within some sort of service that the Thread invoked, the fact that there were multiple DSNs could be hidden from the scheduling thread. For instance, an IEmailService might have the following contract:
public interface IEmailService
{
void SendEmailsToValidAddresses();
}
and the implementation might look something like this:
public class MultipleSourcesEmailService : IEmailService
{
private IEnumerable<IDatabaseSource> databases;
public EmailService(params IDatabaseSource[] sources)
{
databases = new List<IDatabaseSource>(sources);
}
public void SendEmailsToValidAddresses()
{
foreach(var database in databases)
{
var emailAddresses = database.SelectAllEmailAddresses();
ValidateAndSendEmailsTo(emailAddresses);
}
}
public void ValidateAndSendEmailsTo(IEnumerable<string> emailAddresses)
{
// Perform appropriate logic
...
}
}
In this way, your timer logic can remain the same and on a single Thread whilst the concern of sending emails is separated into the IEmailService. This also means that you could implement a SingleSourceEmailService and a MultipleSourceEmailService and swap the multiple sources in when you're code complete and the consumer of the service need never know.
Of course, the EmailService as implemented above will SendEmails from multiple sources sequentially - if you need it to run in parallel you could change the EmailService to kick off a new Thread for each of the DSNs that you have, you could even call it the: MultiThreadedMultipleSourceEmailService but as a consumer of the IEmailService your scheduling will never know the difference.
Use a backgroundworker.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Just start one for each DSN, very smooth and easy to use.
Try using System.Threading.Timer
Here is sample code from my project, Hope this helps
public void StartDSNTimers()
{
_tmr1 = new Timer(CheckMessages, dsn1, 0, 60000);
_tmr2 = new Timer(CheckMessages, dsn2, 0, 60000);
_tmr3 = new Timer(CheckMessages, dsn3, 0, 60000);
}
private void CheckMessages(object obj)
{
//Logic
}

Categories

Resources