I need help trying to understand the Observer Pattern and Delegates. I found this code on another website and I am trying to understand what it is actually doing. Can someone help me out.
When I execute the code, I get both of the messages "Server is up and running" and "Server is down, We are working on it it will be back soon". I think I am getting both of the message because in the Main, there is a server.ServerStatus = true; and a server.ServerStatus = false. However, if I comment out the server.ServerStatus = true; and run then I I get the message "Server is up and running" but I expected to only see "Server is down, We are working on it it will be back soon.". Can someone explain?
Susan
class Program
{
static void Main(string[] args)
{
Server server = new Server();
server.ServerStatusChanged += new EventHandler(ProcessServerStatus);
server.ServerStatus = true;
server.ServerStatus = false;
Console.Read();
}
public class Server
{
public event EventHandler ServerStatusChanged;
private bool _ServerStatus;
public bool ServerStatus
{
get { return this._ServerStatus; }
set {
if (this._ServerStatus == value) return; // Dont need to do anything;
if (this.ServerStatusChanged != null) // make sure the invocation list is not empty
ServerStatusChanged(value, new EventArgs()); // Firing Event
this._ServerStatus = value;
}
}
}
public static void ProcessServerStatus(object sender, EventArgs e)
{
bool status = (bool)sender;
if (status)
Console.WriteLine("Server is up and running");
else
Console.WriteLine("Server is down, We are working on it it will be back soon");
}
}
Here is code that does what you want.
Notice the _initialized variable. This is needed because otherwise nothing happens the the first time through. You would have to set the status to True before it starts working correctly.
Also, I what you describe didn't happen for me. I didn't get the message saying it was up and running. I didn't get anything at all.
class Program
{
static void Main(string[] args)
{
Server server = new Server();
server.ServerStatusChanged += new EventHandler(ProcessServerStatus);
//server.ServerStatus = true;
server.ServerStatus = false;
Console.Read();
}
public class Server
{
public event EventHandler ServerStatusChanged;
private bool _ServerStatus = false;
private bool _initialized = false;
public bool ServerStatus
{
get { return this._ServerStatus; }
set
{
if (this._initialized == true && this._ServerStatus == value)
return; // Dont need to do anything;
else
this._initialized = true;
if (this.ServerStatusChanged != null) // make sure the invocation list is not empty
ServerStatusChanged(value, new EventArgs()); // Firing Event
this._ServerStatus = value;
}
}
}
public static void ProcessServerStatus(object sender, EventArgs e)
{
bool status = (bool)sender;
if (status)
Console.WriteLine("Server is up and running");
else Console.WriteLine("Server is down, We are working on it it will be back soon");
}
}
This is an event registration ...
server.ServerStatusChanged += new EventHandler(ProcessServerStatus);
It says "whenever there is a ServerStatusChanged, call the ProcesServerStatus method." So, when you set the ServerStatus to true, you get a call to ProcessServerStatus passing true and it prints "Server is up..."; when you change it to false, another event is fired, but this time ServerStatus is false, so you get "Server is down ..." So, you are watching the server status and doing something (ProcessServerStatus) when it changes.
Essentially what is occuring is that the assignments you have in Main that are setting ServerStatus to true/false will call the "set" function of the ServerStatus property.
Within that function, an event is created and fired to the ServerStatusChanged handler, which will be received by anyone who registered for events. Also note that the event is fired only if the status changes.
In this case, Main registered for the event in the
Server.ServerStatusChanged += new EventHandler(ProcessServerStatus);
line.
Since you set ServerStatus to true, then false in Main, two events are fired, causing your console to display both "Server is up and running", and "Server is down, We are working on it...."
If you haven't come across the Observer pattern before, it might be instinctive to think that all method calls are done quite statically - the methods we call in our code are what's called. However, the observer pattern lets you do things more dynamically. You can have something say at runtime "hey, when such and such happens, let me know! You can let me know by calling such and such a method" The one being observed keeps a record of everyone that has said to it "when such and such happens, let me know" - and then, when it happens, it does just that - lets all of them know. This is done by keeping a list of the methods which must be called, and then calling them all when the event is 'triggered'.
The line:
server.ServerStatusChanged += new EventHandler(ProcessServerStatus);
is telling the server object "hey, when the server status is changed, let me know. You can let me know by calling the ProcessServerStatus method". And so, when the line:
server.ServerStatus = true;
is run, it triggers the ServerStatusChanged event, which then calls everything that has said "let me know". So it will call the ProcessServerStatus method, since that's the only method in its list.
Every method that is in that list must have a certain method signature; in this case, void ProcessServerStatus(object sender, EventArgs e). The sender parameter in this case is the server status - you can see this in the line:
ServerStatusChanged(value, new EventArgs());
And so when ProcessServerStatus is called, it casts it so a boolean value, and prints the appropriate string.
When I run this code, it works as expected - if I comment out the serverStatus = true; line, it prints nothing (since _serverStatus defaults to false, and the event is not triggered if the status hasn't changed). Instead, if I comment out the serverStatus = false; line, it says "the server is up and running"
Related
Sorry for the title, i didn't find it easy to resume.
My issue is that I need to implement a c# dll that implements a 'scan' method, but this scan, when invoked, must not block the main thread of the application using the dll. Moreover, it is a duty that after the scan resolves it rises an Event.
So my issue (in the deep) is that i'm not so experienced at c#, and after very hard investigation i've come up with some solutions but i'm not very sure if they are the "right" procedures.
In the dll i've come up with:
public class Reader
{
public delegate void ReaderEventHandler(Object sender, AlertEventArgs e);
public void Scan(String ReaderName)
{
AlertEventArgs alertEventArgs = new AlertEventArgs();
alertEventArgs.uuiData = null;
//Code with blocking scan function here
if (ScanFinnished)
{
alertEventArgs.uuiData = "Scan Finnished!";
}
alertEventArgs.cardStateData = readerState[0].eventState;
ReaderEvent(new object(), alertEventArgs);
}
public event ReaderEventHandler ReaderEvent;
}
public class AlertEventArgs : EventArgs
{
#region AlertEventArgs Properties
private string _uui = null;
private uint cardState = 0;
#endregion
#region Get/Set Properties
public string uuiData
{
get { return _uui; }
set { _uui = value; }
}
public uint cardStateData
{
get { return cardState; }
set { cardState = value; }
}
#endregion
}
While in the main app I do:
Reader reader;
Task polling;
String SelectedReader = "Some_Reader";
private void bButton_Click(object sender, EventArgs e)
{
reader = new Reader();
reader.ReaderEvent += new Reader.ReaderEventHandler(reader_EventChanged);
polling = Task.Factory.StartNew(() => reader.Scan(SelectedReader));
}
void reader_EventChanged(object sender, AlertEventArgs e)
{
MessageBox.Show(e.uuiData + " Estado: " + e.cardStateData.ToString("X"));
reader.Dispose();
}
So here, it works fine but i don't know if it's the proper way, in addition i'm not able to handle possible Exceptions generated in the dll.
Also tried to use async/await but found it difficult and as I understand it's just a simpler workaround Tasks.
What are the inconvinients of this solution? how can i capture Exceptions (are they in other threads and that's why i cant try/catch them)? Possible concept faults?
When your class sends events, the sender usually is that class, this. Having new object() as sender makes absolutely no sense. Even null would be better but... just use this.
You shouldn't directly raise events as it might result in race conditions. Might not happen easily in your case but it's just a good guideline to follow. So instead of calling ReaderEvent(new object(), alertEventArgs); call RaiseReaderEvent(alertEventArgs); and create method for it.
For example:
private void RaiseReaderEvent(AlertEventArgs args)
{
var myEvent = ReaderEvent; // This prevents race conditions
if (myEvent != null) // remember to check that someone actually subscribes your event
myEvent(this, args); // Sender should be *this*, not some "new object()".
}
Though I personally like a bit more generic approach:
private void Raise<T>(EventHandler<T> oEvent, T args) where T : EventArgs
{
var eventInstance = oEvent;
if (eventInstance != null)
eventInstance(this, args);
}
Which can then be used to raise all events in same class like this:
Raise(ReaderEvent, alertEventArgs);
Since your scan should be non-blocking, you could use tasks, async/await or threads for example. You have chosen Tasks which is perfectly fine.
In every case you must understand that when you are not blocking your application, your application's main thread continues going like a train. Once you jump out of that train, you can't return. You probably should declare a new event "ErrorEvent" that is raised if your scan-procedure catches an exception. Your main application can then subscribe to that event as well, but you still must realize that those events are not (necessarily) coming from the main thread. When not, you won't be able to interact with your GUI directly (I'm assuming you have one due to button click handler). If you are using WinForms, you'll have to invoke all GUI changes when required.
So your UI-thread safe event handler should be something like this:
void reader_EventChanged(object sender, AlertEventArgs e)
{
if (InvokeRequired) // This true for others than UI Thread.
{
Invoke((MethodInvoker)delegate
{
Text = "My new title!";
});
}
else
Text = "My new title!";
}
In WPF there's Dispather that handles similar invoking.
My program works like this:
I press a radio button which opens the port.
Next i press a button "Read" which starts a thread that reads data continously from the Serial Port using port.ReadLine() and prints it in a textbox;
I have another radio which should first join the thread and after that close the port;the problem is the printing goes well until i close the port when the UI freezes.
public Form1()
{
mythread = new Thread(ReadFct);
myPort = new SerialPort("COM3", 9600);
myPort.ReadTimeout = 3500;
InitializeComponent();
foreach (var t in Constants.ComboParameters)
this.paramCombo.Items.Add(t);
radioClose.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
radioOpen.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
}
Below is the function attached to the thread
void ReadFct()
{
string aux = "";
while (readCondition)
{
if (myPort.IsOpen)
aux = myPort.ReadLine();
this.SetText(aux);
}
}
Below is the radio button event handler
public void radioButtonCheckedChanged(object sender,EventArgs e)
{
if (radioOpen.Checked && !myPort.IsOpen)
try
{
myPort.Open();
mythread.Start();
}
catch (Exception)
{
MessageBox.Show("Nu s-a putut deschide port-ul");
}
if (radioClose.Checked && myPort.IsOpen)
{
readCondition = false;
mythread.Join();
myPort.Close();
// myPort.DataReceived -= DataReceivedHandler;
}
}
The read button function:
private void readbtn_Click(object sender, EventArgs e)
{
if (!myPort.IsOpen)
MessageBox.Show("PORT NOT OPENED!");
else
{
// myPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
readCondition = true;
if (!mythread.IsAlive)
{
mythread = new Thread(ReadFct);
mythread.Start();
}
}
I have used what MSDN suggest when changing control from another thread:
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
StringTb del = new StringTb(SetText);
this.Invoke(del, new object[] { text });
}
else
SetData = text;
}
It's hard to know exactly what you need, lacking a good Minimal, Complete, and Verifiable code example to illustrate the question. That said, the issue here is that the Thread.Join() method causes that thread to stop doing any other work, and the thread you use to call that method is the thread that handles all of the user interface. Worse, if your port never receives another newline, the thread you're waiting on will never terminate, because you're stuck waiting on the ReadLine() method. Even worse, even if you do get a newline, if that happens while you're stuck waiting on the Thread.Join(), the call to Invoke() will deadlock, because it needs the UI thread to do its work, and the Thread.Join() call is preventing it from getting the UI thread.
In other words, your code has multiple problems, any one of which could cause problems, but all of which together mean it just can't possibly work.
There are a variety of strategies to fix this, but IMHO the best is to use await. The first step in doing that is to change your I/O handling so that it's done asynchronously instead of dedicating a thread to it:
// Ideally, you should rename this method to "ReadFctAsync". I am leaving
// all names intact for the same of the example though.
async Task ReadFct()
{
string aux = "";
using (StreamReader reader = new StreamReader(myPort.BaseStream))
{
while (true)
{
aux = await reader.ReadLineAsync();
// This will automatically work, because the "await" will automatically
// resume the method execution in the UI thread where you need it.
this.SetText(aux);
}
}
}
Then, instead of creating a thread explicitly, just create a Task object by calling the above:
public Form1()
{
// In this approach, you can get rid of the "mythread" field altogether
myPort = new SerialPort("COM3", 9600);
myPort.ReadTimeout = 3500;
InitializeComponent();
foreach (var t in Constants.ComboParameters)
this.paramCombo.Items.Add(t);
radioClose.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
radioOpen.CheckedChanged += new EventHandler(radioButtonCheckedChanged);
}
public async void radioButtonCheckedChanged(object sender,EventArgs e)
{
if (radioOpen.Checked && !myPort.IsOpen)
{
try
{
myPort.Open();
await ReadFct();
// Execution of this method will resume after the ReadFct() task
// has completed. Which it will do only on throwing an exception.
// This code doesn't have any continuation after the "await", except
// to handle that exception.
}
catch (Exception)
{
// This block will catch the exception thrown when the port is
// closed. NOTE: you should not catch "Exception". Figure out what
// *specific* exceptions you expect to happen and which you can
// handle gracefully. Any other exception can mean big trouble,
// and doing anything other than logging and terminating the process
// can lead to data corruption or other undesirable behavior from
// the program.
MessageBox.Show("Nu s-a putut deschide port-ul");
}
// Return here. We don't want the rest of the code executing after the
// continuation, because the radio button state might have changed
// by then, and we really only want this call to do work for the button
// that was selected when the method was first called. Note that it
// is probably even better if you just break this into two different
// event handlers, one for each button that might be checked.
return;
}
if (radioClose.Checked && myPort.IsOpen)
{
// Closing the port should cause `ReadLineAsync()` to throw an
// exception, which will terminate the read loop and the ReadFct()
// task
myPort.Close();
}
}
In the above, I have completely ignored the readbtn_Click() method. Lacking a good MCVE, it's not clear what role that button plays in the overall scheme. You seem to have a radio button group (of two buttons) that control whether the port is open or closed. It is not clear why then you have an additional regular button that is seemingly able to also open the port and start reading, independently of the radio group.
If you want that extra button, it seems to me that all it ought to do is change the radio group state, by checking the "open" radio button. Then let the radio group buttons handle the port state and reading. If you need more specific advice as to how to fully integrate my code example above with your entire UI, you will need to provide more detail, preferably in a new question. That new question must include a good MCVE.
After making the project simpler, I believe I identified the problem is actually a result the async marshalling.
UPDATE: I made the code simpler to try to figure out what was going on. So here is an update... The Observable collection is being populated on a new thread (async method). I tried moving the assigning of the ItemsSource to after the ObservableCollection is loaded as seen below
async void LoadAllData(object sender, EventArgs e)
{
if (sender != null)
{
App.GeoLocationComplete -= LoadAllData;
}
await ViewModelObjects.NearbyLocations.LoadLocationData();
lvPlaces.ItemsSource = ViewModelObjects.NearbyLocations.GBSLocationDetails;
}
The definition for the data load method is a follows:
public async Task LoadLocationData()
{....}
When I run this code I get the following error:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I know what is causing the error (the data was loaded on a thread other than the UI thread) but I don't know how to fix it. Suggestions?
UPDATE UPDATE: So I believe I have identified the root cause of the problem but have not figured out how to fix it. I started by simplifying my code as follows and it worked.
public nearbyplaces()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
LoadAllData(null, null);
}
void LoadAllData(object sender, EventArgs e)
{
lobj_Places = new ObservableCollection<GBSLocationDetail>()
{
new GBSLocationDetail()
{
Title = "Location 1",
Distance = "20 Miles",
AddInfo = "Something Else",
AttributesTexts="Gay, Bar, Dance"
}
};
lvPlaces.ItemsSource = lobj_Places;
}
HOWEVER, what I need is for the LoadAllData method to be called once I have the GPS location from the device. So in my App.XAML.cs I have the following delegate event declared:
public static Plugin.Geolocator.Abstractions.IGeolocator gobj_RealGeoCoordinator;
public static event GeoLocationCompleteEventHandler GeoLocationComplete;
public static bool gb_WaitingForLocation = true;
Then I have the following code call the event once I get the location back from the device:
private async void ProcessStartupandResume()
{
if (gobj_RealGeoCoordinator == null)
{
gobj_RealGeoCoordinator = CrossGeolocator.Current;
ViewModelObjects.AppSettings.CanAccessLocation = App.gobj_RealGeoCoordinator.IsGeolocationEnabled;
if (!ViewModelObjects.AppSettings.CanAccessLocation)
{
await MainPage.DisplayAlert(ResourceStrings.GetValue("NoLocationServicesTitle"), ResourceStrings.GetValue("NoLocationServicesMessage"), ResourceStrings.GetValue("OKButtonText"));
}
//Only add the events if the object has to be created.
gobj_RealGeoCoordinator.PositionChanged += gobj_RealGeoCoordinator_PositionChanged;
gobj_RealGeoCoordinator.PositionError += (sender, e) =>
{
ProcessException(new Exception(e.Error.ToString()));
};
}
//Set this to null to trigger the first check
ib_GPSReenabled = null;
if (gobj_RealGeoCoordinator.IsListening)
await gobj_RealGeoCoordinator.StopListeningAsync();
gobj_RealGeoCoordinator.DesiredAccuracy = 50;
await gobj_RealGeoCoordinator.StartListeningAsync(10000, 20);
}
private static void gobj_RealGeoCoordinator_PositionChanged(object sender, PositionEventArgs e)
{
var pos = e.Position;
ViewModelObjects.AppSettings.Latitude = pos.Latitude;
ViewModelObjects.AppSettings.Longitude = pos.Longitude;
if (gb_WaitingForLocation)
{
gb_WaitingForLocation = false;
GeoLocationComplete?.Invoke(new object() , null);
}
}
Then in my page I subscribe to the GeoLocationComplete event using the LoadAllData method as seen below. Even when I use a local object and try to set the ItemsSource for the ListView in the code when executed as a result of the event being raised, I receive the error. See code below which subscribed to the event:
public nearbyplaces()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
if (App.gb_WaitingForLocation)
App.GeoLocationComplete += LoadAllData;
else
LoadAllData(null, null);
}
Any suggestions on how to fix this?
OK so I figured it out. I needed to invoke the event on the main thread and I did that with the following code:
Device.BeginInvokeOnMainThread(() =>
{
GeoLocationComplete?.Invoke(new object(), null);
});
After inserting this code, the error was gone. Changing the code back to simply
GeoLocationComplete?.Invoke(new object(), null);
cause the error to occur again. Thus I believe this resolved my problem. Hope this helps someone else. :)
I am completely lost in what is really causing the problem. So rather trying to explain the problem, I might as well as get straight to the code with the problem. Here is the layout of my program:
private void connection_OnMessage(object sender, agsXMPP.protocol.client.Message msg)
{
if (!string.IsNullOrEmpty(msg.Body) && ((msg.XDelay != null && msg.XDelay.Stamp.Date == DateTime.Today) || msg.XDelay == null))
{
agsXMPP.Jid JID = new Jid(msg.From.Bare);
int rowIndex = chatLog.Rows.Add();
chatLog.Rows[rowIndex].Cells["chatNameColumn"].Value = JID.User;
chatLog.Rows[rowIndex].Cells["chatMessageColumn"].Value = msg.Body;
//Begin line of the problem
if (IncomingMessage != null)
IncomingMessage(this, JID.User, msg.Body);
//End of the problem
}
}
The above code snippet is of class A. After starting up the program, this class makes the connection to the server. Right after being connected, this code snippet is rapidly fired about 20 times, once per line of message. (There are already about 20 lines of message in the chat log.) Since only one message makes it through the if condition, the lines commented with the problem is only run once. Those lines fire the code snippet below of class B.
(Around the time class A is firing, I have another class like A that fires the similar event to be handled by class B the same way, which will be handled by class C.)
private void newSource_IncomingMessage(IChatSource sender, string user, string message)
{
UpdatedMessageEventHandler temp = UpdatedMessage;
if (temp != null)
temp(sender, user, message);
}
The above code snippet of class B fires the code snippet below of class C.
private void chatManager_UpdatedMessage(IChatSource source, string user, string message)
{
if (!source.Muted)
{
updateMessage(source, user, message);
}
}
delegate void UpdateMessageCallback(IChatSource source, string user, string message);
private void updateMessage(IChatSource source, string user, string message)
{
if (allDataGridView.InvokeRequired)
{
UpdateMessageCallback d = new UpdateMessageCallback(updateMessage);
Invoke(d, new object[] { source, user, message });
}
else
{
int row = allDataGridView.Rows.Add();
allDataGridView.Rows[row].DefaultCellStyle.ForeColor = source.TextColor;
allDataGridView.Rows[row].Cells["NameColumn"].Value = user;
allDataGridView.Rows[row].Cells["MessageColumn"].Value = message;
if (!MenuItem.Checked)
{
MenuItem.Checked = true;
Show();
}
}
}
Here is what I tried to do to fix the problem, but the code is removed already:
I tried adding lock to certain codes.
I tried to put the certain codes on a separate thread and have them run.
Here is what happened:
When I run the program, the UI thread seems to be blocked. In other words, class C doesn't get painted. Sometimes, the form doesn't even appear.
A few times, it complaint about a strange error "An error occurred invoking the method. The destination thread no longer exists."
If I commented out the problem lines, everything work fine, but here is the strange part. If I create a timer object in class A and have it fired the event the same way, it works fine.
While line stepping in debug mode, I sometimes got it work fine, but majority of the time, it fails.
For a few times, I run into InvalidOperationException with the message, "Control accessed from a thread other than the thread it was created on." even though I did make it thread safe.
In conclusion, I don't know what is causing the UI thread to be blocked. Any pointer or what I might do wrong?
The problem is that you're calling methods crossthreading. This can lead to deadlocks.
You could solve this, adding the messages on a concurrent queue and on a timer (gui thread) checking the queue and adding the messages to controls.
This is not a complete solution, but a method to prevent crossthread method invoking
Like: (PSEUDO) (wrote online on the site)
// dataholder
public class ChatMsg
{
public string User {get;set;}
public string Message {get;set;}
}
// message store
private List<ChatMsg> _messages = new List<ChatMsg>();
// timer
private Timer _timer;
// callback for you chatapi?? (like you wrote)
private void newSource_IncomingMessage(IChatSource sender, string user, string message)
{
UpdatedMessageEventHandler temp = UpdatedMessage;
// lock the store
lock(_messages)
_messages.Add(new ChatMsg { User = user, Message = message });
}
// constructor
public Form1()
{
// create the check timer.
_timer = new Timer();
_timer.Interval = 100;
_timer.Tick += Timer_Tick;
_timer.Start();
}
// timer method
private void Timer_Tick(object sender, EventArgs e)
{
// copy of the queue
ChatMsg[] msgs;
// lock the store and 'move' the messages
lock(_messages)
{
msgs = _messages.ToArray();
_messages.Clear();
}
if(msgs.Length == 0)
return;
// add them to the controls
foreach(var msg in msgs)
{
// add the message to the gui controls... (copied from your question)
int row = allDataGridView.Rows.Add();
allDataGridView.Rows[row].DefaultCellStyle.ForeColor = source.TextColor;
allDataGridView.Rows[row].Cells["NameColumn"].Value = user;
allDataGridView.Rows[row].Cells["MessageColumn"].Value = message;
}
}
Something like that..
I'm just starting to force myself to use Entity Framework (I know I'm a long way behind), but I've fallen at the first hurdle.
The program I'm writing simply watches a PLC for a bit to become true. When it does become true an event fires and then values (random at the moment) will be entered into SQL. I then set the bit to false.
The problem I'm having is that the event fires once, the entries get added, and bit gets set false. However the event never triggers again. If I comment out all of the SQL bits then and just set the bit false then it works perfectly and fires multiple times.
Here is most of the code that I've got.
Any help would be really appreciated.
static void Main(string[] args)
{
PlcListener plcListener = new PlcListener();
plcLister.BitChanged += (bitVal) => On_BitChanged(bitVal, plcListener)
plcListner.Start();
}
private static void On_BitChanged(bool bitVal, PlcListener plcListner)
{
SqlEntities sqlEntity = new SqlEntities();
SampleData sampleData = new SampleData(){ Data = new Random().Next(); };
sqlEntity.AddToSampleDatas(sampleData);
sqlEntity.SaveChanges();
plcListener.Confirm();
}
public class PlcListener
{
public void Start()
{
OPCServer opcServer = new OPCServer();
opcServer.DataChanged += On_DataChanged;
}
public void Confirm()
{
//Code to set bit to false
}
public void On_DataChanged(bool bitVal.......)
{
if(bitVal)
{
BitChangedEventHandler handler = BitChanged;
if (handler != null)
{
handler(bitVal);
}
}
}
public delegate void BitChangedEventHandler(bool bitValue);
public event BitChangedEventHandler BitChanged;
}
You did not mention which OpcServer component you are using, but the most probable problem is that the DataChange comes into your application in a COM callback thread and you are not allowed to write back to COM while handling the callback. You need to just record the incoming value and do the actual handling in your own handler thread, which then sets the bit back to false in the PLC.