|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
Asynchronous Programming Model - how to implement?I'm trying to implement APM (Asynchronous Programming Model) API by myself - I mean: IAsyncResult BeginMyFunc(AsyncCallback, object @object); void EndMyFunc(IAsyncResult result); functions pair. The problem is that my working thread has to report updates to the GUI by System.Windows.Forms.Control.Invoke(Delegate method) invocation. How to implement my EndMyFunc(IAsyncResult result) function? It should be something like this inside: result.AsyncWaitHandle.WaitOne(); where AsyncWaitHandle is ManualResetEvent or something like this. Unfortunately, this approach will prevent my GUI thread from handling SendMessage calls coming from working thread invocations of System.Windows.Forms.Control.Invoke(Delegate method). Deadlock is about to happen. The same problem is about terminating phase. I cannot just call this by GUI thread: workingThread.Join(); because of System.Windows.Forms.Control.Invoke(Delegate method) invocations coming from the working thread. The deadlock again. What I need is some kind of .NET replacement for MsgWaitForMultipleObjects + some necessary message pumping. How to properly implement it in .NET 2.0? many thanks in advance, -- dmitry Try using the System.ComponentModel.BackgroundWorker component.
http://msdn2.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx -- Show quoteHTH, Kevin Spencer Microsoft MVP Software Composer http://unclechutney.blogspot.com I had the same problem once. Fixed it using the same solution. "Dmitry Nogin" <dmitryno***@hotmail.com> wrote in message news:e8Nkn3ZWHHA.5092@TK2MSFTNGP03.phx.gbl... > Hi, > > I'm trying to implement APM (Asynchronous Programming Model) API by > myself - > I mean: > > IAsyncResult BeginMyFunc(AsyncCallback, object @object); > void EndMyFunc(IAsyncResult result); > > functions pair. The problem is that my working thread has to report > updates > to the GUI by System.Windows.Forms.Control.Invoke(Delegate method) > invocation. > > How to implement my EndMyFunc(IAsyncResult result) function? It should be > something like this inside: > > result.AsyncWaitHandle.WaitOne(); > > where AsyncWaitHandle is ManualResetEvent or something like this. > Unfortunately, this approach will prevent my GUI thread from handling > SendMessage calls coming from working thread invocations of > System.Windows.Forms.Control.Invoke(Delegate method). Deadlock > is about to happen. > > The same problem is about terminating phase. I cannot just call this by > GUI > thread: > > workingThread.Join(); > > because of System.Windows.Forms.Control.Invoke(Delegate method) > invocations > coming from the working thread. The deadlock again. > > What I need is some kind of .NET replacement for MsgWaitForMultipleObjects > + > some necessary message pumping. How to properly implement it in .NET 2.0? > > > > many thanks in advance, > > -- dmitry > > > > > Kevin, Thank you a lot!
but I really need another level of flexibility here. I need APM API where void EndMyFunc(IAsyncResult result); GUI thread call is blocking as it has to be. From other hand my working thread should be able to SendMessage to GUI thread while it is blocked this way. Are there _any_ chances to do it in .NET? I'm ready to write my own WaitOne() method implementation - just tell me how to properly pump messages in .NET, if possible. . . Show quote "Kevin Spencer" <unclechut***@nothinks.com> wrote in message news:uEqibbaWHHA.4404@TK2MSFTNGP03.phx.gbl... > Try using the System.ComponentModel.BackgroundWorker component. > > http://msdn2.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx > > -- > HTH, > > Kevin Spencer > Microsoft MVP > Software Composer > http://unclechutney.blogspot.com > > I had the same problem once. Fixed it using the same solution. > > "Dmitry Nogin" <dmitryno***@hotmail.com> wrote in message > news:e8Nkn3ZWHHA.5092@TK2MSFTNGP03.phx.gbl... >> Hi, >> >> I'm trying to implement APM (Asynchronous Programming Model) API by >> myself - >> I mean: >> >> IAsyncResult BeginMyFunc(AsyncCallback, object @object); >> void EndMyFunc(IAsyncResult result); >> >> functions pair. The problem is that my working thread has to report >> updates >> to the GUI by System.Windows.Forms.Control.Invoke(Delegate method) >> invocation. >> >> How to implement my EndMyFunc(IAsyncResult result) function? It should be >> something like this inside: >> >> result.AsyncWaitHandle.WaitOne(); >> >> where AsyncWaitHandle is ManualResetEvent or something like this. >> Unfortunately, this approach will prevent my GUI thread from handling >> SendMessage calls coming from working thread invocations of >> System.Windows.Forms.Control.Invoke(Delegate method). Deadlock >> is about to happen. >> >> The same problem is about terminating phase. I cannot just call this by >> GUI >> thread: >> >> workingThread.Join(); >> >> because of System.Windows.Forms.Control.Invoke(Delegate method) >> invocations >> coming from the working thread. The deadlock again. >> >> What I need is some kind of .NET replacement for >> MsgWaitForMultipleObjects + >> some necessary message pumping. How to properly implement it in .NET 2.0? >> >> >> >> many thanks in advance, >> >> -- dmitry >> >> >> >> >> > > On Feb 26, 7:29 am, "Dmitry Nogin" <dmitryno***@hotmail.com> wrote: Dmitry,> Kevin, Thank you a lot! > > but I really need another level of flexibility here. I need APM API where > > void EndMyFunc(IAsyncResult result); > > GUI thread call is blocking as it has to be. From other hand my working > thread should be able to SendMessage to GUI thread while it is blocked this > way. Are there _any_ chances to do it in .NET? I'm ready to write my own > WaitOne() method implementation - just tell me how to properly pump messages > in .NET, if possible. . . > The WaitOne implementations in .NET *will* pump messages. Are you observing a different behavior? Brian "Brian Gideon" <briangid***@yahoo.com> wrote in message news:1172499097.614613.315660@m58g2000cwm.googlegroups.com... Dear Brian,> The WaitOne implementations in .NET *will* pump messages. Are you > observing a different behavior? You can try to run the following code snippet (Visual C# Windows Application, VS2005): using System; using System.Windows.Forms; using System.Threading; namespace WindowsApplication5 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { ManualResetEvent mre = new ManualResetEvent(false); Thread thread = new Thread((ThreadStart)delegate { Thread.Sleep(500); Invoke((ThreadStart)delegate // blocks here { MessageBox.Show("Trying to send message..."); }); mre.Set(); }); thread.Start(); mre.WaitOne(); // and here } } } Please let me know if you would like me to explain this sample. . . Thank you for your attention - I really appreciate it. 8-) Sorry, I forgot to remove Thread.Sleep(500); - it means nothing...
"Dmitry Nogin" <dmitryno***@hotmail.com> wrote in message news:%23DX6QVbWHHA.496@TK2MSFTNGP06.phx.gbl... Dear Brian,"Brian Gideon" <briangid***@yahoo.com> wrote in message news:1172499097.614613.315660@m58g2000cwm.googlegroups.com... > The WaitOne implementations in .NET *will* pump messages. Are you > observing a different behavior? You can try to run the following code snippet (Visual C# Windows Application, VS2005): using System; using System.Windows.Forms; using System.Threading; namespace WindowsApplication5 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { ManualResetEvent mre = new ManualResetEvent(false); Thread thread = new Thread((ThreadStart)delegate { Thread.Sleep(500); Invoke((ThreadStart)delegate // blocks here { MessageBox.Show("Trying to send message..."); }); mre.Set(); }); thread.Start(); mre.WaitOne(); // and here } } } Please let me know if you would like me to explain this sample. . . Thank you for your attention - I really appreciate it. 8-) Hi,
Okay, I may need to review this in a little more in detail myself. That was a great example by the way. Replace your Form_Load code with this: private void Form1_Load(object sender, EventArgs e) { ManualResetEvent mre = new ManualResetEvent(false); Thread thread = new Thread((ThreadStart)delegate { BeginInvoke((ThreadStart)delegate // blocks here { MessageBox.Show("Message"); }); MessageBox.Show("Before WaitHandle.Set()"); mre.Set(); MessageBox.Show("After WaitHandle.Set()"); }); thread.Start(); mre.WaitOne(); // and here MessageBox.Show("Done"); } Notice that I changed it to use BeginInvoke instead and I've added message boxes to help visualize the order of what's going on. You'll see that WaitOne does indeed pump messages, but it only does so *after* Set is called. So it appears that WaitOne is alertable, but only when signalled. Since your example was using Invoke the WaitHandle never got signalled. This was not what I was expecting. I'll have to do more research. Brian Hi,
I replaced WaitOne() to thread.Join() - we can see the same behaviour while documentation states that Join function "Blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping." P.S. It's about my very basic scenario: to use PostMessage from GUI to Working thread and SendMessage from working thread to GUI. Working thread might be busy, so we need some kind of message queue and GUI thread should react immediately and can return some user responses to the business logic requests if necessary. From one hand - I need asynchronous business object logic execution (on a working thread) to be used in Windows Forms, etc. From another hand - I need synchronous execution (on a GUI thread /or/ working thread + GUI thread blocking) for job automation scripting, etc. Everything is OK, but my legacy underlying API doesn't support multiple threads, so I have to use working thread all the time + optional GUI thread blocking. And I'd like my business objects to be databindable so I have to raise all the events by my GUI thread. Please, give me some tip! I'm about to change my development platform from Win32 to .NET, so I read from side to side these 16 books in 6 months (http://www.amazon.com/My-NET-readings/lm/R1E1A68BY7S8N0), but I'm still very far from clear understanding of many .NET aspects. . . 8-) Now I'm reading about "Multithreaded Programming with the Event-based Asynchronous Pattern" but it looks like that my blocking problem is actual anyway. . . Show quote "Brian Gideon" <briangid***@yahoo.com> wrote in message news:1172506755.041530.293080@t69g2000cwt.googlegroups.com... > Hi, > > Okay, I may need to review this in a little more in detail myself. > That was a great example by the way. Replace your Form_Load code with > this: > > private void Form1_Load(object sender, EventArgs e) > { > ManualResetEvent mre = new ManualResetEvent(false); > > Thread thread = new Thread((ThreadStart)delegate > { > BeginInvoke((ThreadStart)delegate // blocks here > { > MessageBox.Show("Message"); > }); > > MessageBox.Show("Before WaitHandle.Set()"); > > mre.Set(); > > MessageBox.Show("After WaitHandle.Set()"); > }); > > thread.Start(); > > mre.WaitOne(); // and here > > MessageBox.Show("Done"); > } > > Notice that I changed it to use BeginInvoke instead and I've added > message boxes to help visualize the order of what's going on. You'll > see that WaitOne does indeed pump messages, but it only does so > *after* Set is called. So it appears that WaitOne is alertable, but > only when signalled. Since your example was using Invoke the > WaitHandle never got signalled. > > This was not what I was expecting. I'll have to do more research. > > Brian > "Jim Rand" <jimr***@ix.netcom.com> wrote in message news:O72BzqnWHHA.4188@TK2MSFTNGP06.phx.gbl... I have to design wrapping for an old ugly C-based API to expose it in synchronous/asynchronous ways to COM/.NET and design new GUI for it. My victim is: http://bhub.com. I've got 3 months only - my family is moving to Canada this summer. 8-)> Just out of curiosity, what business task are you trying to accomplish? I wrote the same for another internal product (w/o any .net) - 230K of C++ (ATL) code lines / 18 months / 1.5 man 8-). What was especially interesting there - I generated 75% of c++ gui code from the xml specification by xslt as a part of compilation process + several interesting custom controls: So if I infer correctly
a.. you have an unmanaged DLL that does file transfer b.. you need to set up these jobs in a .NET front-end to run immediately or at some scheduled time c.. you need some kind of monitoring capability Presumably you want these activities to run on separate background threads. The reason for my curiosity is that your approach seems very complicated. The approach I've used is to use the canned asynchronous threading that is part of .NET. Here is some sample code you might find useful. In this case, the JobScheduler has no knowledge of the actual tasks performed. It only runs the JobDelegate. It does know how the job finished returned via the callback. Thread collisions are handled very simply via the _lockObj. There is no explicit wait or joining of threads or any other complicated stuff. To set up a job: /* Start background job scheduler - give it a ISynchronizeInvokebto invoke methods on the GUI thread */ Helpers.LookupTableSingleton.Instance.dsLookupTbls.GUISync = (System.ComponentModel.ISynchronizeInvoke)this; _jobScheduler = new AgencyInfo.Helpers.JobScheduler(); int interval = Int32.Parse(AppInfo.Settings("LookupTableRefresh")); _jobScheduler.TaskAdd(new AgencyInfo.Helpers.JobSchedulerTask("LookupTableUpdate", DateTime.Now + new TimeSpan(0, interval, 0), new TimeSpan(0, interval, 0), Helpers.LookupTableSingleton.Instance.dsLookupTbls.UpdateLookupTables)); _jobScheduler.Start(new TimeSpan(0, 0, 30)); The delegate ---------------------------------------------------------------------------------------------------- /* Update lookup tables with new or changed records. */ internal Helpers.JobSchedulerTaskOutcome UpdateLookupTables() { bool ok = false; bool hasNewData = false; _dsNewData.Clear(); try { foreach (System.Data.SqlClient.SqlDataAdapter da in _adapterList) { string tableName = da.TableMappings[0].DataSetTable; if (tableName != "Notes") { da.SelectCommand.Parameters["@MaxTS"].Value = _maxTSList[tableName]; } da.Fill(_dsNewData, tableName); if (_dsNewData.Tables[tableName].Rows.Count > 0) hasNewData = true; } } catch (System.Data.SqlClient.SqlException ex) { _log.Error("SqlException UpdateLookupTables", ex); } catch (System.Exception ex) { _log.Error("Exception UpdateLookupTables", ex); } if ( hasNewData ) { ok = UpdateDataSet(); } else { ok = true; } return new AgencyInfo.Helpers.JobSchedulerTaskOutcome("LookupTableUpdate", ok); } /* The final data merging has to be done on the GUI thread */ private delegate bool UpdateDataSetDelegate(); private bool UpdateDataSet() { bool ok = false; if (_GUISync.InvokeRequired) { ok = (bool) _GUISync.Invoke(new UpdateDataSetDelegate(UpdateDataSet), null); } else { try { this.AcceptChanges(); this.Merge(_dsNewData, false); this.AcceptChanges(); NoteMaxTimestamps(); ok = true; } catch (System.Exception ex) { _log.Warn("UpdateDataSet merge failed", ex); ok = false; } return ok; } return ok; } The Job Scheduler ---------------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Runtime.Remoting.Messaging; namespace AgencyInfo.Helpers { class JobScheduler { private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(typeof(JobScheduler)); private object _lockObj = new object(); private Dictionary<string, JobSchedulerTask> _taskDictionary; private TimeSpan _threadSleepTimeSpan; private Thread _RunThread; #region Constructors public JobScheduler() { _taskDictionary = new Dictionary<string,JobSchedulerTask>(); } #endregion #region Public methods public void Start(TimeSpan threadSleepTimeSpan) { _threadSleepTimeSpan = threadSleepTimeSpan; _RunThread = new Thread(new ThreadStart(Run)); _RunThread.IsBackground = true; _RunThread.Start(); } public void Stop() { _RunThread.Abort(); } public void TaskAdd(JobSchedulerTask task) { lock ( _lockObj ) { if ( !_taskDictionary.ContainsKey(task.TaskName) ) { _taskDictionary.Add(task.TaskName, task); } } } public void TaskRemove(string taskName) { lock (_lockObj) { if ( _taskDictionary.ContainsKey(taskName) ) { _taskDictionary.Remove(taskName); } } } #endregion #region ThreadStuff private void Run() { try { while (true) { lock (_lockObj) { foreach (JobSchedulerTask task in _taskDictionary.Values) { if ((!task.IsBroken) && (!task.IsRunning) && (DateTime.Now >= task.StartAt)) { task.IsRunning = true; AsyncCallback callback = new AsyncCallback(JobFinished); IAsyncResult ar = task.JobToRun.BeginInvoke(callback, null); } } } Thread.Sleep(_threadSleepTimeSpan); } } catch (ThreadAbortException) { } catch (Exception ex) { _log.Error("Exception in Run", ex); } finally { _taskDictionary.Clear(); _taskDictionary = null; } } private void JobFinished(IAsyncResult ar) { JobSchedulerTask.JobDelegate jobDelegate = (JobSchedulerTask.JobDelegate)((AsyncResult)ar).AsyncDelegate; JobSchedulerTaskOutcome outcome = jobDelegate.EndInvoke(ar); lock (_lockObj) { if ( _taskDictionary.ContainsKey(outcome.TaskName) ) { _taskDictionary[outcome.TaskName].IsRunning = false; _taskDictionary[outcome.TaskName].IsBroken = !outcome.FinishedOK; _taskDictionary[outcome.TaskName].StartAt = DateTime.Now + _taskDictionary[outcome.TaskName]._timeSpan; } } } #endregion } class JobSchedulerTask { public readonly TimeSpan _timeSpan; public readonly string TaskName; private DateTime _StartAt; private bool _isRunning = false; private bool _isBroken = false; public readonly JobDelegate JobToRun; public JobSchedulerTask(string taskName, DateTime startAt, TimeSpan timeSpan, JobDelegate jobDelegate) { this.TaskName = taskName; this._timeSpan = timeSpan; this._StartAt = startAt; this.JobToRun = jobDelegate; } public bool IsBroken { get { return _isBroken; } set { _isBroken = value; } } public bool IsRunning { get { return _isRunning; } set { _isRunning = value; } } public DateTime StartAt { get { return _StartAt; } set { _StartAt = value; } } public delegate JobSchedulerTaskOutcome JobDelegate(); } class JobSchedulerTaskOutcome { public readonly string TaskName; public readonly bool FinishedOK; public JobSchedulerTaskOutcome(string taskName, bool finishedOK) { this.TaskName = taskName; this.FinishedOK = finishedOK; } } } "Jim Rand" <jimr***@ix.netcom.com> wrote in message Sorry for taking so long to get back to you. Please have a look at somenews:%23cm8uhpWHHA.4860@TK2MSFTNGP04.phx.gbl... comments below. > So if I infer correctly I'd like to use Command design pattern to simplify implementation of> you have an unmanaged DLL that does file transfer > you need to set up these jobs in a .NET front-end to run immediately or at > some scheduled time > you need some kind of monitoring capability > Presumably you want these activities to run on separate background > threads. > The reason for my curiosity is that your approach seems very complicated. Undo/Redo in the GUI but we are talking about same approaches. > The approach I've used is to use the canned asynchronous threading that is You don't need them because you are not going to expose synchronous version> part of .NET. > Here is some sample code you might find useful. In this case, the > JobScheduler has no knowledge > of the actual tasks performed. It only runs the JobDelegate. It does know > how the job finished returned > via the callback. Thread collisions are handled very simply via the > _lockObj. > There is no explicit wait or joining of threads or any other complicated > stuff. of your API. I mean you have something like this: void UpdateLookupTablesAsync() { _jobScheduler.TaskAdd(new AgencyInfo.Helpers.JobSchedulerTask("LookupTableUpdate", DateTime.Now + new TimeSpan(0, interval, 0), new TimeSpan(0, interval, 0), Helpers.LookupTableSingleton.Instance.dsLookupTbls.UpdateLookupTables)); } but you don't expose synchronous version: void UpdateLookupTables() which blocks execution till the job completion. Usually we need asynchronous versions to be used in WinForms, while synchronous versions are suitable for batch files and WebForms. There are some design patterns about: http://msdn2.microsoft.com/en-us/library/ms228969.aspx "The .NET Framework provides two design patterns for asynchronous operations: ž Asynchronous operations that use IAsyncResult objects. ž Asynchronous operations that use events." So, I have to say that .NET has a very, very serious multithreading problem. Hi Dimitry,
Show quote > How about this? - Part of the the data is fetched immediately and the rest > You don't need them because you are not going to expose synchronous > version > of your API. I mean you have something like this: > > void UpdateLookupTablesAsync() > { > _jobScheduler.TaskAdd(new > AgencyInfo.Helpers.JobSchedulerTask("LookupTableUpdate", > DateTime.Now + new TimeSpan(0, interval, 0), > new TimeSpan(0, interval, 0), > > Helpers.LookupTableSingleton.Instance.dsLookupTbls.UpdateLookupTables)); > } > > but you don't expose synchronous version: > > void UpdateLookupTables() > > which blocks execution till the job completion. > > Usually we need asynchronous versions to be used in WinForms, while > synchronous versions are suitable for batch files and WebForms. There are > some design patterns about: > is fetched in background. Note the use of the ticket. If the tickets don't match, the background fill is aborted. ------------------------------------------------------------------------------------ using System; using System.Data; using System.Threading; using System.Runtime.Remoting.Messaging; using log4net; namespace JimRand.AgencyInfo.Datasets { partial class DSAgencyForm { private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(typeof(DSAgencyForm)); private System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter> _adapterList = new System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter>(); private System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter> _adapterListBkGrnd = new System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter>(); private System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter> _adapterListForeGrnd = new System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter>(); private string _lastUpdatedBy; private DataAdapters.DAAgencyForm _adapterSet; private System.Data.DataSet _dsClone; private int _ticket = 0; private object _lockObj = new object(); /* Dataset must be updated on the GUI thread - see UpdateLookupTables() */ private System.ComponentModel.ISynchronizeInvoke _GUISync; internal void Fill(int officeID) { lock (_lockObj) { _ticket++; /* Set parameters for selected officeID */ _adapterSet.daNotes.SelectCommand.Parameters["@ParentTblID"].Value = officeID; _adapterSet.daOffice.SelectCommand.Parameters["@OfficeID"].Value = officeID; _adapterSet.daOfficeCnsltnt.SelectCommand.Parameters["@OfficeID"].Value = officeID; _adapterSet.daOfficeEnduserMM.SelectCommand.Parameters["@OfficeID"].Value = officeID; _adapterSet.daOfficeIdxUrl.SelectCommand.Parameters["@OfficeID"].Value = officeID; _adapterSet.daRefUrls.SelectCommand.Parameters["@OfficeID"].Value = officeID; _adapterSet.daUrlCnsltSolution.SelectCommand.Parameters["@OfficeID"].Value = officeID; _adapterSet.daUrlFraming.SelectCommand.Parameters["@OfficeID"].Value = officeID; _adapterSet.daUrlOfficeConsultant.SelectCommand.Parameters["@OfficeID"].Value = officeID; _adapterSet.daUrlOfficeEnduser.SelectCommand.Parameters["@OfficeID"].Value = officeID; /* Fill tables Office, Notes and OifficeIdxUrl on GUI thread */ Helpers.DataAccessLayer.Fill((System.Data.DataSet)this, _adapterListForeGrnd); /* Fill remaining tables on background thread */ BackGrndFillDelegate d = new BackGrndFillDelegate(BackGrndFill); System.AsyncCallback ac = new System.AsyncCallback(BackGrndFillCallback); System.IAsyncResult ar = d.BeginInvoke(_ticket, ac, null); } } /* internal void Fill */ private delegate DatasetPackage BackGrndFillDelegate(int ticket); private DatasetPackage BackGrndFill(int ticket) { /* Dataset to fill on the background thread */ System.Data.DataSet ds; lock (_dsClone) { ds = _dsClone.Clone(); } try { foreach (System.Data.SqlClient.SqlDataAdapter da in _adapterListBkGrnd) { lock (_lockObj) { if (ticket == _ticket) { string tableName = da.TableMappings[0].DataSetTable; da.Fill(ds, tableName); } else { break; } } } } catch (Exception ex) { _log.Error("BackGrndFill", ex); } return new DatasetPackage(ticket, ds); } private void BackGrndFillCallback(System.IAsyncResult ar) { BackGrndFillDelegate d = (BackGrndFillDelegate)((AsyncResult)ar).AsyncDelegate; DatasetPackage dsp = d.EndInvoke(ar); MergeData(dsp); } private delegate void MergeDataDelegate(DatasetPackage dsp); private void MergeData(DatasetPackage dsp) { if ( _GUISync.InvokeRequired ) { _GUISync.Invoke(new MergeDataDelegate(MergeData), new object[] { dsp }); } else { lock (_lockObj) { if (dsp.Ticket == _ticket) { /* Only merge if an update has not been started */ if (!this.HasChanges()) this.Merge(dsp.DS); } } } } internal void PrepareDatasetForUse(System.ComponentModel.ISynchronizeInvoke guiSync) { _GUISync = guiSync; _adapterSet = new JimRand.AgencyInfo.DataAdapters.DAAgencyForm(); _lastUpdatedBy = Thread.CurrentPrincipal.Identity.Name; /* All adapters - use during update */ _adapterList.Add(_adapterSet.daOffice); _adapterList.Add(_adapterSet.daNotes); _adapterList.Add(_adapterSet.daOfficeIdxUrl); _adapterList.Add(_adapterSet.daUrlOfficeConsultant); _adapterList.Add(_adapterSet.daUrlCnsltSolution); _adapterList.Add(_adapterSet.daUrlFraming); _adapterList.Add(_adapterSet.daUrlOfficeEnduser); _adapterList.Add(_adapterSet.daRefUrls); _adapterList.Add(_adapterSet.daOfficeCnsltnt); _adapterList.Add(_adapterSet.daOfficeEnduserMM); /* Foreground - fill immediate */ _adapterListForeGrnd.Add(_adapterSet.daOffice); _adapterListForeGrnd.Add(_adapterSet.daNotes); _adapterListForeGrnd.Add(_adapterSet.daOfficeIdxUrl); /* Background - fill async */ _adapterListBkGrnd.Add(_adapterSet.daUrlOfficeConsultant); _adapterListBkGrnd.Add(_adapterSet.daUrlCnsltSolution); _adapterListBkGrnd.Add(_adapterSet.daUrlFraming); _adapterListBkGrnd.Add(_adapterSet.daUrlOfficeEnduser); _adapterListBkGrnd.Add(_adapterSet.daRefUrls); _adapterListBkGrnd.Add(_adapterSet.daOfficeCnsltnt); _adapterListBkGrnd.Add(_adapterSet.daOfficeEnduserMM); Helpers.DataAccessLayer.FlipToProduction(_adapterList); /* Model dataset for background filling */ _dsClone = this.Clone(); _dsClone.Relations.Clear(); _dsClone.Tables["Notes"].Constraints.Remove("FK_Office_Notes"); _dsClone.Tables["OfficeCnsltnt"].Constraints.Remove("FK_Office_OfficeCnsltnt"); _dsClone.Tables["OfficeEnduserMM"].Constraints.Remove("FK_Office_OfficeEnduserMM"); _dsClone.Tables["OfficeIdxUrl"].Constraints.Remove("FK_Office_OfficeIdxUrl"); _dsClone.Tables["UrlOfficeEnduser"].Constraints.Remove("FK_OfficeIdxUrl_UrlOfficeEnduser"); _dsClone.Tables["UrlFraming"].Constraints.Remove("FK_OfficeIdxUrl_UrlFraming"); _dsClone.Tables["UrlOfficeConsultant"].Constraints.Remove("FK_OfficeIdxUrl_UrlOfficeConsultant"); _dsClone.Tables["RefUrls"].Constraints.Remove("FK_OfficeIdxUrl_RefUrls"); _dsClone.Tables.Remove("Office"); _dsClone.Tables.Remove("Notes"); _dsClone.Tables.Remove("OfficeIdxUrl"); } /* internal void PrepareDatasetForUse */ internal void TearDown() { _adapterSet.Dispose(); _adapterList.Clear(); _adapterList = null; _adapterListForeGrnd.Clear(); _adapterListForeGrnd = null; _adapterListBkGrnd.Clear(); _adapterListBkGrnd = null; } /* internal void TearDown */ /* Update all tables in the backend */ internal void Update() { /* Copy of changes used by DSLookupTbls.RefreshImmediate to determine which tables to refresh immediatedly */ DataSet dsChanges = this.GetChanges(); Helpers.DataAccessLayer.Update((System.Data.DataSet)this, _adapterList, true, _lastUpdatedBy); /* Immediate refill of lookup tables impacted */ Helpers.LookupTableSingleton.Instance.dsLookupTbls.RefreshImmediate(dsChanges); dsChanges.Dispose(); } /* internal void Update */ } internal class DatasetPackage { public readonly System.Data.DataSet DS; public readonly int Ticket; internal DatasetPackage(int ticket, System.Data.DataSet ds) { this.Ticket = ticket; this.DS = ds; } } } "Jim Rand" <jimr***@ix.netcom.com> wrote in message Yep, it looks very natural. Unfortunately, my legacy underlying API allows news:OphbVmyXHHA.688@TK2MSFTNGP03.phx.gbl... >> You don't need them because you are not going to expose synchronous >> version of your API. I mean you have something like this: > How about this? - Part of the the data is fetched immediately and the rest > is fetched in background. Note the use of the ticket. If the tickets > don't match, the background fill is aborted. one thread only. I mean I cannot use two different threads interchangeably. So I really need some alertable blocking in GUI thread while working one does the entire job here. BTW, there is very intresting blog somehow related to this topic:
"STAs, pumping, and the UI" http://www.bluebytesoftware.com/blog/PermaLink,guid,6bdb2b54-a042-4eab-8cef-390603a58beb.aspx Show quote "Jim Rand" <jimr***@ix.netcom.com> wrote in message news:OphbVmyXHHA.688@TK2MSFTNGP03.phx.gbl... > Hi Dimitry, > >> >> You don't need them because you are not going to expose synchronous >> version >> of your API. I mean you have something like this: >> >> void UpdateLookupTablesAsync() >> { >> _jobScheduler.TaskAdd(new >> AgencyInfo.Helpers.JobSchedulerTask("LookupTableUpdate", >> DateTime.Now + new TimeSpan(0, interval, 0), >> new TimeSpan(0, interval, 0), >> >> Helpers.LookupTableSingleton.Instance.dsLookupTbls.UpdateLookupTables)); >> } >> >> but you don't expose synchronous version: >> >> void UpdateLookupTables() >> >> which blocks execution till the job completion. >> >> Usually we need asynchronous versions to be used in WinForms, while >> synchronous versions are suitable for batch files and WebForms. There are >> some design patterns about: >> > > > How about this? - Part of the the data is fetched immediately and the rest > is fetched in background. Note the use of the ticket. If the tickets > don't match, the background fill is aborted. > > ------------------------------------------------------------------------------------ > using System; > using System.Data; > using System.Threading; > using System.Runtime.Remoting.Messaging; > using log4net; > > namespace JimRand.AgencyInfo.Datasets > { > > partial class DSAgencyForm > { > > private static readonly log4net.ILog _log = > log4net.LogManager.GetLogger(typeof(DSAgencyForm)); > private > System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter> > _adapterList = new > System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter>(); > private > System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter> > _adapterListBkGrnd = new > System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter>(); > private > System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter> > _adapterListForeGrnd = new > System.Collections.Generic.List<System.Data.SqlClient.SqlDataAdapter>(); > private string _lastUpdatedBy; > private DataAdapters.DAAgencyForm _adapterSet; > private System.Data.DataSet _dsClone; > private int _ticket = 0; > private object _lockObj = new object(); > > /* Dataset must be updated on the GUI thread - see UpdateLookupTables() > */ > private System.ComponentModel.ISynchronizeInvoke _GUISync; > > internal void Fill(int officeID) > { > > lock (_lockObj) > { > > _ticket++; > > /* Set parameters for selected officeID */ > _adapterSet.daNotes.SelectCommand.Parameters["@ParentTblID"].Value = > officeID; > _adapterSet.daOffice.SelectCommand.Parameters["@OfficeID"].Value = > officeID; > _adapterSet.daOfficeCnsltnt.SelectCommand.Parameters["@OfficeID"].Value > = officeID; > > _adapterSet.daOfficeEnduserMM.SelectCommand.Parameters["@OfficeID"].Value > = officeID; > _adapterSet.daOfficeIdxUrl.SelectCommand.Parameters["@OfficeID"].Value > = officeID; > _adapterSet.daRefUrls.SelectCommand.Parameters["@OfficeID"].Value = > officeID; > > _adapterSet.daUrlCnsltSolution.SelectCommand.Parameters["@OfficeID"].Value > = officeID; > _adapterSet.daUrlFraming.SelectCommand.Parameters["@OfficeID"].Value = > officeID; > > _adapterSet.daUrlOfficeConsultant.SelectCommand.Parameters["@OfficeID"].Value > = officeID; > > _adapterSet.daUrlOfficeEnduser.SelectCommand.Parameters["@OfficeID"].Value > = officeID; > > > /* Fill tables Office, Notes and OifficeIdxUrl on GUI thread */ > Helpers.DataAccessLayer.Fill((System.Data.DataSet)this, > _adapterListForeGrnd); > > /* Fill remaining tables on background thread */ > BackGrndFillDelegate d = new BackGrndFillDelegate(BackGrndFill); > System.AsyncCallback ac = new > System.AsyncCallback(BackGrndFillCallback); > System.IAsyncResult ar = d.BeginInvoke(_ticket, ac, null); > } > > } /* internal void Fill */ > > private delegate DatasetPackage BackGrndFillDelegate(int ticket); > private DatasetPackage BackGrndFill(int ticket) > { > > /* Dataset to fill on the background thread */ > System.Data.DataSet ds; > lock (_dsClone) > { > ds = _dsClone.Clone(); > } > > try > { > foreach (System.Data.SqlClient.SqlDataAdapter da in _adapterListBkGrnd) > { > lock (_lockObj) > { > if (ticket == _ticket) > { > string tableName = da.TableMappings[0].DataSetTable; > da.Fill(ds, tableName); > } > else > { > break; > } > } > } > > } > catch (Exception ex) > { > _log.Error("BackGrndFill", ex); > } > > return new DatasetPackage(ticket, ds); > } > > private void BackGrndFillCallback(System.IAsyncResult ar) > { > BackGrndFillDelegate d = > (BackGrndFillDelegate)((AsyncResult)ar).AsyncDelegate; > DatasetPackage dsp = d.EndInvoke(ar); > MergeData(dsp); > } > > private delegate void MergeDataDelegate(DatasetPackage dsp); > private void MergeData(DatasetPackage dsp) > { > if ( _GUISync.InvokeRequired ) > { > _GUISync.Invoke(new MergeDataDelegate(MergeData), new object[] { > dsp }); > } > else > { > lock (_lockObj) > { > if (dsp.Ticket == _ticket) > { > /* Only merge if an update has not been started */ > if (!this.HasChanges()) this.Merge(dsp.DS); > } > > } > } > } > > internal void > PrepareDatasetForUse(System.ComponentModel.ISynchronizeInvoke guiSync) > { > > _GUISync = guiSync; > > _adapterSet = new JimRand.AgencyInfo.DataAdapters.DAAgencyForm(); > > _lastUpdatedBy = Thread.CurrentPrincipal.Identity.Name; > > /* All adapters - use during update */ > _adapterList.Add(_adapterSet.daOffice); > _adapterList.Add(_adapterSet.daNotes); > _adapterList.Add(_adapterSet.daOfficeIdxUrl); > _adapterList.Add(_adapterSet.daUrlOfficeConsultant); > _adapterList.Add(_adapterSet.daUrlCnsltSolution); > _adapterList.Add(_adapterSet.daUrlFraming); > _adapterList.Add(_adapterSet.daUrlOfficeEnduser); > _adapterList.Add(_adapterSet.daRefUrls); > _adapterList.Add(_adapterSet.daOfficeCnsltnt); > _adapterList.Add(_adapterSet.daOfficeEnduserMM); > > /* Foreground - fill immediate */ > _adapterListForeGrnd.Add(_adapterSet.daOffice); > _adapterListForeGrnd.Add(_adapterSet.daNotes); > _adapterListForeGrnd.Add(_adapterSet.daOfficeIdxUrl); > > /* Background - fill async */ > _adapterListBkGrnd.Add(_adapterSet.daUrlOfficeConsultant); > _adapterListBkGrnd.Add(_adapterSet.daUrlCnsltSolution); > _adapterListBkGrnd.Add(_adapterSet.daUrlFraming); > _adapterListBkGrnd.Add(_adapterSet.daUrlOfficeEnduser); > _adapterListBkGrnd.Add(_adapterSet.daRefUrls); > _adapterListBkGrnd.Add(_adapterSet.daOfficeCnsltnt); > _adapterListBkGrnd.Add(_adapterSet.daOfficeEnduserMM); > > Helpers.DataAccessLayer.FlipToProduction(_adapterList); > > /* Model dataset for background filling */ > _dsClone = this.Clone(); > _dsClone.Relations.Clear(); > _dsClone.Tables["Notes"].Constraints.Remove("FK_Office_Notes"); > > _dsClone.Tables["OfficeCnsltnt"].Constraints.Remove("FK_Office_OfficeCnsltnt"); > > _dsClone.Tables["OfficeEnduserMM"].Constraints.Remove("FK_Office_OfficeEnduserMM"); > > _dsClone.Tables["OfficeIdxUrl"].Constraints.Remove("FK_Office_OfficeIdxUrl"); > > _dsClone.Tables["UrlOfficeEnduser"].Constraints.Remove("FK_OfficeIdxUrl_UrlOfficeEnduser"); > > _dsClone.Tables["UrlFraming"].Constraints.Remove("FK_OfficeIdxUrl_UrlFraming"); > > _dsClone.Tables["UrlOfficeConsultant"].Constraints.Remove("FK_OfficeIdxUrl_UrlOfficeConsultant"); > > _dsClone.Tables["RefUrls"].Constraints.Remove("FK_OfficeIdxUrl_RefUrls"); > > _dsClone.Tables.Remove("Office"); > _dsClone.Tables.Remove("Notes"); > _dsClone.Tables.Remove("OfficeIdxUrl"); > > } /* internal void PrepareDatasetForUse */ > > internal void TearDown() > { > _adapterSet.Dispose(); > _adapterList.Clear(); > _adapterList = null; > _adapterListForeGrnd.Clear(); > _adapterListForeGrnd = null; > _adapterListBkGrnd.Clear(); > _adapterListBkGrnd = null; > } /* internal void TearDown */ > > /* Update all tables in the backend */ > internal void Update() > { > > /* Copy of changes used by DSLookupTbls.RefreshImmediate to determine > which tables to refresh immediatedly */ > DataSet dsChanges = this.GetChanges(); > > Helpers.DataAccessLayer.Update((System.Data.DataSet)this, _adapterList, > true, _lastUpdatedBy); > > /* Immediate refill of lookup tables impacted */ > > Helpers.LookupTableSingleton.Instance.dsLookupTbls.RefreshImmediate(dsChanges); > > dsChanges.Dispose(); > > } /* internal void Update */ > > } > > internal class DatasetPackage > { > public readonly System.Data.DataSet DS; > public readonly int Ticket; > > internal DatasetPackage(int ticket, System.Data.DataSet ds) > { > this.Ticket = ticket; > this.DS = ds; > } > } > > } > I'm not so good in pinvoke (I've got no expirience at all 8-) - could
somebode be so kind to check this stuff? Now it works without blocking but I'm not sure about possible consequences: private void Form1_Load(object sender, EventArgs e) { //EventWaitHandle done = new ManualResetEvent(false); EventWaitHandle done = new AlertableEvent(false, EventResetMode.ManualReset); Thread thread = new Thread((ThreadStart)delegate { Invoke((ThreadStart)delegate { MessageBox.Show("Trying to send message..."); }); done.Set(); }); thread.Start(); done.WaitOne(); } // where AlertableEvent is done using MsgWaitForMultipleObjectsEx class AlertableEvent : EventWaitHandle { public AlertableEvent(bool initialState, EventResetMode mode) : base(initialState, mode) {} public override bool WaitOne() { IntPtr[] handleArray = {this.Handle}; Message msg; while (true) switch(MsgWaitForMultipleObjectsEx(1, handleArray, INFINITE, QueueStatusFlags.QS_ALLEVENTS, 0)) { case WAIT_OBJECT_0: // event is signaled return true; case WAIT_OBJECT_0 + 1: // input is available while (PeekMessage(out msg, IntPtr.Zero, 0, 0, PeekMessageParams.PM_REMOVE)) DispatchMessage(ref msg); continue; default: return false; } } [Flags] private enum QueueStatusFlags : uint { QS_KEY = 0x0001, QS_MOUSEMOVE = 0x0002, QS_MOUSEBUTTON = 0x0004, QS_POSTMESSAGE = 0x0008, QS_TIMER = 0x0010, QS_PAINT = 0x0020, QS_SENDMESSAGE = 0x0040, QS_HOTKEY = 0x0080, QS_ALLPOSTMESSAGE = 0x0100, QS_RAWINPUT = 0x0400, QS_MOUSE = QS_MOUSEMOVE | QS_MOUSEBUTTON, QS_INPUT = QS_MOUSE | QS_KEY | QS_RAWINPUT, QS_ALLEVENTS = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY, QS_ALLINPUT = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE }private const int WAIT_OBJECT_0 = 0; private const uint INFINITE = 0xFFFFFFFF; [DllImport("user32.dll")] private static extern uint MsgWaitForMultipleObjectsEx(uint nCount, IntPtr[] pHandles, uint dwMilliseconds, QueueStatusFlags dwWakeMask, uint dwFlags); [StructLayout(LayoutKind.Sequential)] public struct Message { public IntPtr handle; public uint msg; public IntPtr wParam; public IntPtr lParam; public uint time; public System.Drawing.Point p; } [Flags] private enum PeekMessageParams : uint { PM_NOREMOVE = 0x0000, PM_REMOVE = 0x0001, PM_NOYIELD = 0x0002, PM_QS_INPUT = QueueStatusFlags.QS_INPUT << 16, PM_QS_POSTMESSAGE = (QueueStatusFlags.QS_POSTMESSAGE | QueueStatusFlags.QS_HOTKEY | QueueStatusFlags.QS_TIMER) << 16, PM_QS_PAINT = QueueStatusFlags.QS_PAINT << 16,PM_QS_SENDMESSAGE = QueueStatusFlags.QS_SENDMESSAGE << 16 } [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool PeekMessage(out Message message, IntPtr handle, uint filterMin, uint filterMax, PeekMessageParams flags); [DllImport("user32.dll")] private static extern IntPtr DispatchMessage([In] ref Message lpmsg); } Show quote "Brian Gideon" <briangid***@yahoo.com> wrote in message news:1172506755.041530.293080@t69g2000cwt.googlegroups.com... > Hi, > > Okay, I may need to review this in a little more in detail myself. > That was a great example by the way. Replace your Form_Load code with > this: > > private void Form1_Load(object sender, EventArgs e) > { > ManualResetEvent mre = new ManualResetEvent(false); > > Thread thread = new Thread((ThreadStart)delegate > { > BeginInvoke((ThreadStart)delegate // blocks here > { > MessageBox.Show("Message"); > }); > > MessageBox.Show("Before WaitHandle.Set()"); > > mre.Set(); > > MessageBox.Show("After WaitHandle.Set()"); > }); > > thread.Start(); > > mre.WaitOne(); // and here > > MessageBox.Show("Done"); > } > > Notice that I changed it to use BeginInvoke instead and I've added > message boxes to help visualize the order of what's going on. You'll > see that WaitOne does indeed pump messages, but it only does so > *after* Set is called. So it appears that WaitOne is alertable, but > only when signalled. Since your example was using Invoke the > WaitHandle never got signalled. > > This was not what I was expecting. I'll have to do more research. > > Brian > Hi Dmitry,
The BackgroundWorker can send messages to the GUI, using the ProgressChanged event. Here's a good example: http://msdn2.microsoft.com/en-us/library/bz33kx67.aspx Show quote "Dmitry Nogin" <dmitryno***@hotmail.com> wrote in message news:eS8gyoaWHHA.5044@TK2MSFTNGP03.phx.gbl... > Kevin, Thank you a lot! > > but I really need another level of flexibility here. I need APM API where > > void EndMyFunc(IAsyncResult result); > > GUI thread call is blocking as it has to be. From other hand my working > thread should be able to SendMessage to GUI thread while it is blocked > this > way. Are there _any_ chances to do it in .NET? I'm ready to write my own > WaitOne() method implementation - just tell me how to properly pump > messages > in .NET, if possible. . . > > > "Kevin Spencer" <unclechut***@nothinks.com> wrote in message > news:uEqibbaWHHA.4404@TK2MSFTNGP03.phx.gbl... >> Try using the System.ComponentModel.BackgroundWorker component. >> >> http://msdn2.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx >> >> -- >> HTH, >> >> Kevin Spencer >> Microsoft MVP >> Software Composer >> http://unclechutney.blogspot.com >> >> I had the same problem once. Fixed it using the same solution. >> >> "Dmitry Nogin" <dmitryno***@hotmail.com> wrote in message >> news:e8Nkn3ZWHHA.5092@TK2MSFTNGP03.phx.gbl... >>> Hi, >>> >>> I'm trying to implement APM (Asynchronous Programming Model) API by >>> myself - >>> I mean: >>> >>> IAsyncResult BeginMyFunc(AsyncCallback, object @object); >>> void EndMyFunc(IAsyncResult result); >>> >>> functions pair. The problem is that my working thread has to report >>> updates >>> to the GUI by System.Windows.Forms.Control.Invoke(Delegate method) >>> invocation. >>> >>> How to implement my EndMyFunc(IAsyncResult result) function? It should >>> be >>> something like this inside: >>> >>> result.AsyncWaitHandle.WaitOne(); >>> >>> where AsyncWaitHandle is ManualResetEvent or something like this. >>> Unfortunately, this approach will prevent my GUI thread from handling >>> SendMessage calls coming from working thread invocations of >>> System.Windows.Forms.Control.Invoke(Delegate method). Deadlock >>> is about to happen. >>> >>> The same problem is about terminating phase. I cannot just call this by >>> GUI >>> thread: >>> >>> workingThread.Join(); >>> >>> because of System.Windows.Forms.Control.Invoke(Delegate method) >>> invocations >>> coming from the working thread. The deadlock again. >>> >>> What I need is some kind of .NET replacement for >>> MsgWaitForMultipleObjects + >>> some necessary message pumping. How to properly implement it in .NET >>> 2.0? >>> >>> >>> >>> many thanks in advance, >>> >>> -- dmitry >>> >>> >>> >>> >>> >> >> > > > > I'm not sure that last one made it through. The BackgroundWorker can send
message to the UI thread, via the ProgressChanged event. Here's a good example: http://msdn2.microsoft.com/en-us/library/bz33kx67.aspx -- Show quoteHTH, Kevin Spencer Microsoft MVP Software Composer http://unclechutney.blogspot.com I had the same problem once. Fixed it using the same solution. "Dmitry Nogin" <dmitryno***@hotmail.com> wrote in message news:eS8gyoaWHHA.5044@TK2MSFTNGP03.phx.gbl... > Kevin, Thank you a lot! > > but I really need another level of flexibility here. I need APM API where > > void EndMyFunc(IAsyncResult result); > > GUI thread call is blocking as it has to be. From other hand my working > thread should be able to SendMessage to GUI thread while it is blocked > this > way. Are there _any_ chances to do it in .NET? I'm ready to write my own > WaitOne() method implementation - just tell me how to properly pump > messages > in .NET, if possible. . . > > > "Kevin Spencer" <unclechut***@nothinks.com> wrote in message > news:uEqibbaWHHA.4404@TK2MSFTNGP03.phx.gbl... >> Try using the System.ComponentModel.BackgroundWorker component. >> >> http://msdn2.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx >> >> -- >> HTH, >> >> Kevin Spencer >> Microsoft MVP >> Software Composer >> http://unclechutney.blogspot.com >> >> I had the same problem once. Fixed it using the same solution. >> >> "Dmitry Nogin" <dmitryno***@hotmail.com> wrote in message >> news:e8Nkn3ZWHHA.5092@TK2MSFTNGP03.phx.gbl... >>> Hi, >>> >>> I'm trying to implement APM (Asynchronous Programming Model) API by >>> myself - >>> I mean: >>> >>> IAsyncResult BeginMyFunc(AsyncCallback, object @object); >>> void EndMyFunc(IAsyncResult result); >>> >>> functions pair. The problem is that my working thread has to report >>> updates >>> to the GUI by System.Windows.Forms.Control.Invoke(Delegate method) >>> invocation. >>> >>> How to implement my EndMyFunc(IAsyncResult result) function? It should >>> be >>> something like this inside: >>> >>> result.AsyncWaitHandle.WaitOne(); >>> >>> where AsyncWaitHandle is ManualResetEvent or something like this. >>> Unfortunately, this approach will prevent my GUI thread from handling >>> SendMessage calls coming from working thread invocations of >>> System.Windows.Forms.Control.Invoke(Delegate method). Deadlock >>> is about to happen. >>> >>> The same problem is about terminating phase. I cannot just call this by >>> GUI >>> thread: >>> >>> workingThread.Join(); >>> >>> because of System.Windows.Forms.Control.Invoke(Delegate method) >>> invocations >>> coming from the working thread. The deadlock again. >>> >>> What I need is some kind of .NET replacement for >>> MsgWaitForMultipleObjects + >>> some necessary message pumping. How to properly implement it in .NET >>> 2.0? >>> >>> >>> >>> many thanks in advance, >>> >>> -- dmitry >>> >>> >>> >>> >>> >> >> > > > > |
|||||||||||||||||||||||