Exiting the SharpDX RenderLoop - c#

SharpDX has a RenderLoop that runs a given delegate in the render loop:
RenderLoop.Run(m_RenderForm, () =>
{
// Do stuff here to render things...
}
What I need to do is exit the render loop somehow.
RenderLoop.Run(m_RenderForm, () =>
{
if (DoINeedToQuit() == true)
{
// What do I put here?
}
}
I can't just return, because that only ends the current loop iteration.

Since I faced this problem as well, I've taken a look at the source code of SharpDX and have found a solution.
Below is the source code of the Run method:
public static void Run(Control form, RenderCallback renderCallback, bool useApplicationDoEvents = false)
{
if (form == null)
throw new ArgumentNullException("form");
if (renderCallback == null)
throw new ArgumentNullException("renderCallback");
form.Show();
using (var renderLoop = new RenderLoop(form) { UseApplicationDoEvents = useApplicationDoEvents })
while (renderLoop.NextFrame())
renderCallback();
}
In the while there's a condition to continue; it'd be enough to modify that condition. You may want to create a static class with the following code:
private static bool mExitLoop = false;
public static void Run(Control form, RenderCallback renderCallback, bool useApplicationDoEvents = false)
{
if (form is null)
throw new ArgumentNullException(nameof(form));
if (renderCallback is null)
throw new ArgumentNullException(nameof(renderCallback));
Contract.EndContractBlock();
form.Show();
using (var renderLoop = new RenderLoop(form) { UseApplicationDoEvents = useApplicationDoEvents })
{
while (renderLoop.NextFrame() && !mExitLoop)
renderCallback();
}
mExitLoop = false;
}
public static void ExitLoop()
{
mExitLoop = true;
}

You can dispose it:
myDXControl.Dispose();
Disposing the control causes the render loop to stop. In order to restart the loop, make sure it is disposed myDXControl.IsDisposed, then reinitialize the control and start the loop over again.

You'd have to exit the application (Application.Exit).
Alternatively, you can do the job you want to do outside of the loop, inside the loop.

A possible solution is to destroy the control the RenderLoop is rendering onto.
For example,
RenderForm.Close();

As one of the initial posters has shown, the code is fairly simple within the loop. The function you have called is for convenience, you could actually just role your own in the form. Don't need to recompile libraries, just add a function to your form if application.exit is not the only exit trigger you want.

Related

Block UI thread but keep handling incoming calls

I'm developing a plugin for a 3D modelling application. For this application, there is also a third party plugin (a render engine) that I would like to automate.
What I do is create a list of Camera List<Camera> cameraViews , iterate trough all of them and tell the render engine to start rendering
foreach ( Camera camera in cameraViews )
{
// tell the modellingApplication to apply camera
modellingApplication.ApplyCameraToView(camera);
// tell the render engine to render the image
string path = "somePathWhereIWantToSaveTheImage"
renderEngine.renderCurrentScene(path)
// .renderCurrentScene() seems to be async, because my code, which is on the UI thread
// continues... so:
// make sure that the image is saved before continuing to the next image
while ( !File.Exists(path) )
{
Thread.Sleep(500);
}
}
However, this wont work. The renderingplugin seems to do some async work but, when doing this async work, it is calling the main thread for retrieving information.
I found a workaround for this: Right after calling the render engine to render, call a MessageBox. This will block the code from continuing but async calls are still beïng handled. I know, this is weird behaviour. Whats even weirder is the fact that my MessageBox gets automatically closed when the renderengine has done calling the UI thread for information and continues in his own process. Making my code continue to the while loop to check if the image is saved on the disk.
foreach ( Camera camera in cameraViews )
{
// tell the modellingApplication to apply camera
modellingApplication.ApplyCameraToView(camera);
// tell the render engine to render the image
string path = "somePathWhereIWantToSaveTheImage"
renderEngine.renderCurrentScene(path)
// .renderCurrentScene() seems to be async, because my code, which is on the UI thread
// continues... so:
// show the messagebox, as this will block the code but not the renderengine.. (?)
MessageBox.Show("Currently processed: " + path);
// hmm, messagebox gets automatically closed, that's great, but weird...
// make sure that the image is saved before continuing to the next image
while ( !File.Exists(path) )
{
Thread.Sleep(500);
}
}
This is wonderful, except for the messagebox part. I don't want to show a messagebox, I just want to pause my code without blocking the entire thread (as calls from the renderengine to the ui thread are still accepted)..
It would've been much easier if the renderengine didn't do his work async..
I don't feel this is the best answer, but it hopefully it's what you are looking for. This is how you block a thread from continuing.
// Your UI thread should already have a Dispatcher object. If you do this elsewhere, then you will need your class to inherit DispatcherObject.
private DispatcherFrame ThisFrame;
public void Main()
{
// Pausing the Thread
Pause();
}
public void Pause()
{
ThisFrame = new DispatcherFrame(true);
Dispatcher.PushFrame(ThisFrame);
}
public void UnPause()
{
if (ThisFrame != null && ThisFrame.Continue)
{
ThisFrame.Continue = false;
ThisFrame = null;
}
}
If you want to still receive and do actions on that thread while blocking intermediately, you can do something like this. This feels, um... kinda hacky, so don't just copy and paste without making sure I didn't make some major mistake typing this out. I haven't had my coffee yet.
// Used while a work item is processing. If you have something that you want to wait on this process. Or you could use event handlers or something.
private DispatcherFrame CompleteFrame;
// Controls blocking of the thread.
private DispatcherFrame TaskFrame;
// Set to true to stop the task manager.
private bool Close;
// A collection of tasks you want to queue up on this specific thread.
private List<jTask> TaskCollection;
public void QueueTask(jTask newTask)
{
//Task Queued.
lock (TaskCollection) { TaskCollection.Add(newTask); }
if (TaskFrame != null) { TaskFrame.Continue = false; }
}
// Call this method when you want to start the task manager and let it wait for a task.
private void FireTaskManager()
{
do
{
if (TaskCollection != null)
{
if (TaskCollection.Count > 0 && TaskCollection[0] != null)
{
ProcessWorkItem(TaskCollection[0]);
lock (TaskCollection) { TaskCollection.RemoveAt(0); }
}
else { WaitForTask(); }
}
}
while (!Close);
}
// Call if you are waiting for something to complete.
private void WaitForTask()
{
if (CompleteFrame != null) { CompleteFrame.Continue = false; }
// Waiting For Task.
TaskFrame = new DispatcherFrame(true);
Dispatcher.PushFrame(TaskFrame);
TaskFrame = null;
}
/// <summary>
/// Pumping block will release when all queued tasks are complete.
/// </summary>
private void WaitForComplete()
{
if (TaskCollection.Count > 0)
{
CompleteFrame = new DispatcherFrame(true);
Dispatcher.PushFrame(CompleteFrame);
CompleteFrame = null;
}
}
private void ProcessWorkItem(jTask taskItem)
{
if (taskItem != null) { object obj = taskItem.Go(); }
}

WPF MultiThreading: How can this still cause an exception? (Careful with that Dispatcher!)

I have the following code in a message handler (that can be invoked on any thread):
private readonly Dictionary<string,IView> _openedViews = new Dictionary<string,IView>();
private readonly object _lockObject = new object();
public MainView()
{
Messenger.Default.Register<ViewChangeMessage>(this, "adminView", m =>
{
var key = m.ViewName;
lock (_lockObject)
{
if (_openedViews.ContainsKey(key) == false)
_openedViews.Add(key, GetView(key));
content.Content = _openedViews[key];
}
//...
});
//...
How can I still get this exception: An element with the same key already exists in the System.Collections.Generic.Dictionary<TKey,TValue>.
The exception is produced if I rapidly cause the message to be sent multiple times.
EDIT: added more context to the code, Messenger is from Galasoft.MVVMLight
Well in that code you posted I don't see any data race.
If GetView cannot cause a data race you could try to replace that entire block of locked code with a ConcurrentDictionary.GetOrAdd:
private readonly ConcurrentDictionary<string,IView> _openedViews =
new ConcurrentDictionary<string,IView>();
public MainView()
{
Messenger.Default.Register<ViewChangeMessage>(this, "adminView", m =>
{
var key = m.ViewName;
content.Content = _openedViews.GetOrAdd(key, GetView(key));
//...
});
//...
Have you made sure that all threads are using the same instance of lockObject? If they're not then it won't stop multiple threads getting to your add code.
Move var key = m.ViewName; inside lock statement.
Here is what happened:
the GetView instantiated a view, which had a long running operation somewhere (waiting on a background thread), and so that waiting wouldn't lock the UI up someone introduced this code:
public static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
and thanks to the PushFrame, a second message was handled ON THE SAME THREAD as the first, hence the lock did nothing to stop it.
Once I've rearranged the code to this, the problem went away:
if (_openedViews.ContainsKey(key) == false)
{
_openedViews.Add(key, null);
_openedViews[key] = ServiceRegistry.GetService<IShellService>().GetView(key);
}

Closing or Hiding forms causes a cross thread error

I am baffled by this simple task i do over and over again.
I have an array of child forms. The array is initiated in another form's constructor:
frmChildren = new ChildGUI[20];
When the user requests to see a child form, i do this:
if (frmChildren[nb] == null)
{
frmChildren[nb] = new ChildGUI();
frmChildren[nb].MdiParent = this.MdiParent;
}
frmChildren[nb].Show();
So far this works. In the background i can download new content for these forms. When a download is finished i fire a ChildChange event. Here is where it stops working.
I simply want to close/hide any forms open then regenerate a new set of -frmChildren = new ChildGUI[20];- here is one of many trials:
for (int i = 0; i < frmChildren.Length;i++ )
{
if (frmChildren[i] != null)
{
//frmChildren[i].BeginInvoke(new EventHandler(delegate
//{
frmChildren[i].Close();
//}));
}
}
frmChildren= new ChildGUI[20];
I get a Cross Thread exception on the .Close(). Notice i've already tried doing an invoke, but doing so bypasses the !=null for some reason. I think it may have something to do with the garbage collector. Anybody have an input?
The problem is that your anonymous method is capturing i - so by the time it's actually invoked in the UI thread, you've got a different value of i, which may be null. Try this:
for (int i = 0; i < frmChildren.Length; i++)
{
ChildGUI control = frmChildren[i];
if (control != null)
{
control.BeginInvoke(new EventHandler(delegate
{
control.Close();
}));
}
}
frmChildren = new ChildGUI[20];
See Eric Lippert's blog post for why introducing a new variable within the loop fixes the problem.
EDIT: If you want to use a foreach loop, it would look like this:
foreach (ChildGUI control in frmChildren)
{
// Create a "new" variable to be captured
ChildGUI copy = control;
if (copy != null)
{
copy.BeginInvoke(new EventHandler(delegate
{
copy.Close();
}));
}
}
frmChildren = new ChildGUI[20];
Just as an aside, you can use the fact that you just want to call a void method to make the code slightly simpler. As this no longer uses an anonymous method, you can make do away with the "inner" variable:
foreach (ChildGUI control in frmChildren)
{
if (control != null)
{
control.BeginInvoke(new MethodInvoker(control.Close));
}
}
frmChildren = new ChildGUI[20];

Using BackgroundWorker to update the UI without freezes...?

I have the following code for population a ListView from a background thread (DoWork calls the PopulateThread method):
delegate void PopulateThreadCallBack(DoWorkEventArgs e);
private void PopulateThread(DoWorkEventArgs e)
{
if (this.InvokeRequired)
{
PopulateThreadCallBack d = new PopulateThreadCallBack(this.PopulateThread);
this.Invoke(d, new object[] { e });
}
else
{
// Ensure there is some data
if (this.DataCollection == null)
{
return;
}
this.Hide();
// Filter the collection based on the filters
List<ServiceCallEntity> resultCollection = this.ApplyFilter();
// Get the current Ids
List<Guid> previousIdList = this.GetUniqueIdList(listView);
List<Guid> usedIdList = new List<Guid>();
foreach (ServiceCallEntity record in resultCollection)
{
if (e.Cancel)
{
this.Show();
return;
}
else
{
// Get the top level entities
UserEntity userEntity = IvdSession.Instance.Collection.GetEngineerEntity(record.UserId);
AssetEntity assetEntity = IvdSession.Instance.Collection.GetAssetEntity(record.AssetId);
SiteEntity siteEntity = IvdSession.Instance.Collection.GetSiteEntity(record.SiteId);
FaultEntity faultEntity = IvdSession.Instance.Collection.GetFaultEntity(record.FaultId);
if (siteEntity == null || userEntity == null || faultEntity == null)
{
continue;
}
else
{
// Get the linked entities
RegionEntity regionEntity = IvdSession.Instance.Collection.GetRegionEntity(siteEntity.RegionId);
StatusEntity statusEntity = IvdSession.Instance.Collection.GetStatusEntity(record.ServiceCallStatus.StatusId);
ListViewItem item = new ListViewItem(siteEntity.SiteName);
item.SubItems.Add(siteEntity.Address);
item.Tag = record;
item.SubItems.Add(regionEntity.Description);
// Handle if an Asset is involved
if (record.AssetId > 0)
item.SubItems.Add(assetEntity.AssetDisplay);
else
item.SubItems.Add("N/A");
item.SubItems.Add(faultEntity.Description);
item.SubItems.Add(userEntity.UserDisplay);
item.SubItems.Add("TODO: Claimed By");
item.SubItems.Add(record.DateTimeStamp.ToString());
IvdColourHelper.SetListViewItemColour(item, false);
this.PopulateItem(item, ref usedIdList);
}
}
}
// Clean up the grid
this.CleanListView(previousIdList, usedIdList);
// Only autosize when allowed and when there are some items in the ListView
if (this.AllowAutoSize && listView.Items.Count > 0)
{
rsListView.AutoSizeColumns(listView);
this.AllowAutoSize = false;
}
this.Show();
}
}
Unfortunately, this causes the UI to freeze whilst in the foreach... is there any way to update/populate the ListView without it freezing the main UI?
A) You probably don't need to use this.Invoke and instead use this.BeginInvoke. Invoke blocks the current thread.
B) You don't need to define your own delegates you can use MethodInvoker
if(this.InvokeRequired) {
this.BeginInvoke(new MethodInvoker(() => PopulateThread(e)));
return;
}
It's much cleaner :)
You are using Control.Invoke to execute just about everything, meaning this code isn't multithreaded at all.
The proper way (involving a Backgroundworker) would be to use the UpdateProgress event to add elements. It is already synchronized.
But since you're hiding the control (or is it the Form ?) during this process you might as well build a List and on completion add it to the Listview. That piece of code shouldn't take long.
Or some sort of combination, adding small lists in an update event. And I wonder about the wisdom of Hide/Show, I expect this to just make the UI flicker. Leave them out or replace with SuspendLayout/Resumelayout.
Pump the events manually with
Application.DoEvents();

Single instance form but not singleton

I cannot understand how this is possible. Please help!!
I have an app with a trayicon. I want a form to be show when the user double clicks the trayicon. I have a problem where it is possible to get 2 or more forms showing by quickly triple or quadruple clicking the trayicon. The reason I don't want a singleton is that I want the form to be released each time it is closed to save memory, maybe this is not a good idea?
I have a field called m_form1.
I have a method called ShowForm1;
I call the method ShowForm1 on the double-click of the TrayIcon.
private Form1 m_form1;
private void ShowForm1()
{
if (m_form1 == null)
{
Trace.WriteLine("*CREATE*" + Thread.CurrentThread.ManagedThreadId.ToString());
m_form1 = new Form1();
m_form1.FormClosed += new FormClosedEventHandler(m_form1_FormClosed);
m_form1.Show();
}
m_form1.BringToFront();
m_form1.Activate();
}
So when Form1 takes a while to construct, then it is possible to create 2 because m_form1 is still null when the second call arrives. Locking does not seem to work as it is the same thread both calls (I'm guessing the UI thread) ie the trace writes out *CREATE*1 twice (below).
[3560] *CREATE*1
[3560] *CREATE*1
Changing the code to include a lock statement does not help me.
private Form1 m_form1;
private object m_Locker = new object();
private void ShowForm1()
{
lock (m_Locker)
{
if (m_form1 == null)
{
Trace.WriteLine("****CREATE****" + Thread.CurrentThread.ManagedThreadId.ToString());
m_form1 = new Form1();
m_form1.FormClosed += new FormClosedEventHandler(m_form1_FormClosed);
m_form1.Show();
}
}
m_form1.BringToFront();
m_form1.Activate();
}
How should I handle this situation?
Thanks guys
Tim.
Have an additional boolean variable, "m_formUnderConstruction" which you test for before constructing the form, and which you set as soon as you've decided to construct it.
The re-entrancy makes all of this a little icky, unfortunately. I've removed the lock, as if this ever gets called from a different thread then you've got the nasty situation of trying to show a form from a different thread to the one it was constructed on.
private Form1 m_form1;
private bool m_underConstruction = false;
private void ShowForm1()
{
if (m_underConstruction)
{
// We're about to show it anyway
return;
}
m_underConstruction = true;
try
{
if (m_form1 == null)
{
m_form1 = new Form1();
m_form1.FormClosed += new FormClosedEventHandler(m_form1_FormClosed);
m_form1.Show();
}
}
finally
{
m_underConstruction = false;
}
m_form1.BringToFront();
m_form1.Activate();
}
Use Interlocked.Increment to change the nr of the tries. If it is 1, open the form, otherwise, don't. And use Interlocked.Decrement after the test or on form's close.
private int openedForms = 0;
private Form1 m_form1;
private void ShowForm1()
{
if (Interlocked.Increment(ref openedForms) = 1)
{
m_form1 = new Form1();
m_form1.FormClosed += new FormClosedEventHandler(m_form1_FormClosed);
m_form1.Show();
}
else
{
Interlocked.Decrement(ref openedForms);
}
if (m_form1 != null)
{
m_form1.BringToFront();
m_form1.Activate();
}
}
private void m_form1_FormClosed(object Sender, EventArgs args)
{
Interlocked.Decrement(ref openedForms);
}
Please see this, it handles all mouse event combinations for NotifyIcon as well as Form1.
More here: http://code.msdn.microsoft.com/TheNotifyIconExample

Categories