I'm not sure if this is possible, but I couldn't find anything when I searched about it.
I have a visual schedule made in WPF that loads and displays appointments. The problem is that it takes a while to load all the visuals and the program becomes unresponsive during that time.
Is it possible to load the appointment visuals and modify the schedule grid in a separate thread while leaving the main thread open for other things? Or possibly keep the schedule grid permanently in a second STA thread so it can do its own thing without interfering with the window?
edit:
Currently what I have:
private static void FillWeek()
{
BindingOperations.EnableCollectionSynchronization(ObservableAppointments, _lockobject);
for (int i = 1; i < 6; i++)
{
FillDay(Date.GetFirstDayOfWeek().AddDays(i).Date);
}
}
private static ObservableCollection<AppointmentUIElement> ObservableAppointments = new ObservableCollection<AppointmentUIElement>();
private static object _lockobject = new object();
public static async Task FillDay(DateTime date)
{
ClearDay(date);
Appointment[] Appointments;
var date2 = date.AddDays(1);
using (var db = new DataBaseEntities())
{
Appointments = (from Appointment a in db.GetDailyAppointments(2, date.Date) select a).ToArray();
}
await Task.Run(()=>
{
foreach (Appointment a in Appointments)
{
var b = new AppointmentUIElement(a, Grid);
ObservableAppointments.Add(b);
}
});
}
private static void ObservableAppointments_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
var a = e.NewItems[0] as AppointmentUIElement;
a.Display();
}
}
private static void ClearDay(DateTime date)
{
var Queue = new Queue<AppointmentUIElement>(Grid.Children.OfType<AppointmentUIElement>().Where(a => a.Appointment.Start.DayOfWeek == date.DayOfWeek));
while (Queue.Count > 0)
{
var x = Queue.Dequeue();
Grid.Children.Remove(x);
ObservableAppointments.Remove(x);
}
var Queue2 = new Queue<GridCell>(Grid.Children.OfType<GridCell>().Where(g => g.Date.Date == date));
while (Queue2.Count > 0)
{
Queue2.Dequeue().AppointmentUIElements.RemoveAll(a => true);
}
}
AppointmentUIElement is derived from Border
Yes
Now the challenge of all this is that visual elements and bound ObservableCollections can only be modified by the UI thread without some additional work. Bound properties that are not collections do not require this.
So lets say you have the "appointment visuals" from the UI bound to an ObservableCollection that has you appointment data in it. What you can do is make your 'search appointments' function async and register your collection for thread synchronization as below. I'm leaving out anything related to INotifyPropertyChange for brevity.
public ObservableCollection<Appointments> Appointments = new ObservableCollection<Appointments>();
private static object _lockobject = new object();
public async Task Load()
{
await Task.Run(() => { /*load stuff into the Appointments collection here */ });
///possibly more code to execute after the task is complete.
}
//in constructor or similar, this is REQUIRED because the collection is bound and must be synchronized for mulththreading operations
BindingOperations.EnableCollectionSynchronization(YourCollection, _lockobject);
There is also a much nastier and not recommended way of modifying UI thread created visual elements.
this.Dispatcher.Invoke(() => {/* do stuff with ui elements or bound things*/});
The gist of what happens is that you call load from the UI thread and when it hits the 'await task.run' it will work the contents of the task in a seperate thread while allowing the UI thread to continue responding to the user. Once the task completes it will then under the hood return to the ui thread to execute whatever else was under it in the load method.
If you forget the EnableCollectionSynchronization part then any attempts to add or remove items inside the task.run will throw an error complainging that you cannot change the contents of a collection in a different thread then the same one it was created with (almost same error as trying to modify a ui element directly).
Comment reply -> the problem with what your doing is here
AppointmentUIElement(a,Grid)
What you really should be doing here is putting the Grid into a custom control that has a bound item template defined that binds to items from the ObservableAppointments which should actually be the appointment data, not UI elements. All of this should be happening through ViewModels on context. The way your doing it will ONLY work if there is just a single thread managing EVERYTHING, as soon as another thread gets involved it will all fall apart on you.
Is it possible to load the appointment visuals and modify the schedule grid in a separate thread while leaving the main thread open for other things? Or possibly keep the schedule grid permanently in a second STA thread so it can do its own thing without interfering with the window?
You could load and display the schedule grid in a separate window that runs on a dedicated dispatcher thread. Please refer to this blog post for an example of how to launch a WPF window in a separate thread.
Keep in mind that an element created on the new thread won't be able to interact with an element created on the main thread though. So you can't simply load the schedule on another thread and then bring it back to the main thread. A visual element can only be accessed from the thread on which it was originally created on.
Related
I'm currently working on an application that calls data from a WCF service and then loads that data into an ObservableCollection(MyListOfBillsCollection). From the OC, I set my datagrid's itemsource to that collection with the example below.
ucLoading.Visibility = Visibility.Visible;
using (TruckServiceClient service = new TruckServiceClient())
{
bills = await service.GetListOfBillsAsync();
foreach (var item in bills)
{
billItem = MyListOfBillsCollection.FirstOrDefault(x => x.Id == item.Id);
if (billItem == null)
{
billItem = new ListOfBillsView();
isNew = true;
}
billItem.Code = item.StockCode;
billItem.Group = item.GroupName;
...
if (isNew)
MyListOfBillsCollection.Add(billItem);
}
}
dgFloor.ItemsSource = MyListOfBillsCollection; //Blocking UI Thread
ucLoading.Visibility = Visibility.Collapsed;
I've got an issue where, when I load that data from the OC and into my datagrid, my UI Thread gets blocked/application freezes and I need to show a 'spinner/loader' to the user that the application is loading the data.
Is it possible to load data into a datagrid and also showing-and-hiding my spinner with two diffirent UI threads? I know it must be possible, and I have done some research but I cannot get my head around it. So I'm posting my clean code(code without me trying to use Application.Current.Dispatcher) here hoping that someone can give me some headers on what to do.
I have tried Async and Await and and used my code example in a Task method that returns a Task with all the code inside and I then used a dispatcher to release the UI work from within the method to the new Thread, but my 'spinners' still will not work correctly and my window still freezes up. In the Task method, I removed the spinners and called the Show/Collapsed code from where I awaited the Task method.
I have a log window in my application, when I have a few thousand logs, filtering them to include or exclude different log levels makes the UI unresponsive for a period of time from the work load. So I have tried to move the heavy lifting to a worker thread, and am having the same issue still.
I am using an ObservableCollection to hold the log information in my model, and I just reference that directly with my ViewModel. I have used BindingOperations.EnableCollectionSynchronization() to let my worker thread update my observable collection without Dispatching it to the UI thread.
I run the following to update the collection on a worker thread with a
Task.Run(new Action(() => FilterList()));
Methods:
private void FilterList()
{
//Necessary even with EnableCollectionSynchronization
App.Current.Dispatcher.Invoke(new Action(() =>
{
FilteredLogEvents.Clear();
}));
foreach (LogEvent log in FilterLogEvents())
{
FilteredLogEvents.Add(log);
}
RaisePropertyChanged("FilteredLogEvents");
FinishedFilteringLogs();
}
//Filters and returns a list of filtered log events
private List<LogEvent> FilterLogEvents()
{
List<LogEvent> selectedEvents = (from x in LogEvents
where ((ViewDebugLogs == true) ? x.Level == "Debug" : false)
|| ((ViewErrorLogs == true) ? x.Level == "Error" : false)
|| ((ViewInfoLogs == true) ? x.Level == "Info" : false)
select x).ToList();
return selectedEvents;
}
This causes the UI to freeze on the foreach. I also tried just newing up an ObservableCollection and then assigning FilteredLogEvents to it with FilteredLogEvents = myNewCollection; this also causes the UI to freeze for a short while during that process.
If I use Thread.Sleep(1) within the foreach loop the UI remains responsive, though this seems like an inelegant and hacky solution.
What do I need to do to make this work?
Edit: A bit more code context from this class (LogEntries)
The callback for FinishedFilteringLogsEventHandler goes back to the ViewModel to change a bool that enables a couple checkboxes when the filtering is complete.
//Constructor
public LogEntries()
{
foreach(NlogViewerTarget target in NLog.LogManager.Configuration.AllTargets.Where(t=>t is NlogViewerTarget).Cast<NlogViewerTarget>())
{
target.RecieveLog += RecieveLog;
}
FilteredLogEvents = new ObservableCollection<LogEvent>();
BindingOperations.EnableCollectionSynchronization(FilteredLogEvents, filteredLogEventsLock);
}
public delegate void FinishedFilteringLogsEvent();
public FinishedFilteringLogsEvent FinishedFilteringLogsEventHandler;
private object filteredLogEventsLock = new object();
public ObservableCollection<LogEvent> FilteredLogEvents { get; set; }
Some thoughts to consider to improve the speed and responsiveness of your code
A few days ago I asked a similar question and someone advised me not to use the threadpool for long running Tasks. The thread pool is a collection of available threads, that can be started swiftly in comparison to starting a traditional thread like System.ComponentModel.BackGroundWorker.
Although it takes more time to create and start a real thread, this is no problem for long running tasks.
The number of threads in the thread pool is limited, so better not use it for longer running tasks.
If you run a task, it is only scheduled to run in the near future when a thread is available. If all threads are busy it will take some time before the thread starts.
The change from Task to Backgroundworker is limited. If you really want to stick to tasks, consider creating an async function:
async void FilteredLogEvents.AddRangeAsync(IEnumerable<LogEvent> logEvents)
or maybe better:
async void FilteredLogEvents.SetAsync(IEnumerable<LogEvent> logEvents)
which does the clear and add in one async call.
Make your own function async:
private async void FilterList()
{
var filteredLogEvents = FilterLogEvents();
var myTask = Task.Run( () => FilteredLogEvents.SetAsync(filteredLogEvents);
// if desired do other things.
// wait until ready:
await myTask();
RaisePropertyChanged("FilteredLogEvents");
FinishedFilteringLogs();
}
By the way: Are you sure that your sequence of logEvents does not change while you are filtering it?
If so, why do you use ToList() instead of returning an IEnumerable and use deferred execution?
If you are not certain: what happens if during the FilterLogEvents your sequence of logEvents changes?
I have a task, which executed async, in part of this task add items in UI run via Dispatcher.BeginInvoke where i update a ObservebleCollection. For thread safe access to collection, i use a semaphoreSlim, but as request to Collection proceed in UI thread and Dispatcher.BeginInvoke also work in UI thread, i receive a dead lock.
private readonly ObservebleCollection<String> parameters = new ObservebleCollection<String>();
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
//Called from UI
public ObservebleCollection<String> Parameters
{
get
{
semaphore.Wait();
var result = this.parameters;
semaphore.Release();
return result;
}
}
public async Task Operation()
{
await semaphore.WaitAsync();
List<String> stored = new List<String>();
foreach (var parameter in currentRobot.GetParametersProvider().GetParameters())
{
stored.Add(parameter.PropertyName);
}
//Can't do add all items in UI at once, because it's take a long time, and ui started lag
foreach (var model in stored)
{
await UIDispatcher.BeginInvoke(new Action(() =>
{
this.parameters.Add(model);
}), System.Windows.Threading.DispatcherPriority.Background);
}
semaphore.Release();
}
And how i received a dead lock:
When i click a button in my program, Operation executed.
When i click a another button, program try access to Parameters property.
And i received a dead lock =D
Problem: in async operation i fill a observeblecollection via Dispatcher.BeginInvoke for each item separately, because if i add all items at once using Dispatcher, UI will lag. So i need a synchronization method for access to a Parameters property, which will wait until Operation ends.
await ensures the code after it will run in the original Synchronization context, in this case, in the UI thread. This makes BeginInvoke unnecessary as the code already runs on the correct thread.
The result is that you are trying to acquire two locks on the same object from the same thread, resulting in deadlock.
If you want thread-safe access to a collection of objects, avoid manually creating locks and use a thread-safe collection like ConcurrentQueue or ConcurrentDictionary.
Apart from that, I can't say I understand what the code tries to achieve as it does nothing in the background or asynchronously. It could easily be a simple method that copies parameters from one collection to another and it would still be thread safe if written properly. You could just write:
var _parameters=new ConcurrentQueue<string>();
....
public void CopyParameters()
{
foreach (var parameter in currentRobot.GetParametersProvider().GetParameters())
{
_parameters.Enqueue(parameter.PropertyName);
}
}
If you use databinding on the Parameters property, just raise PropertyChanged after you add all entries
What is the real problem you are trying to solve?
UPDATE
It seems the real problem is the UI freezes if you try to add too many items at a time. This isn't a threading problem, it's a WPF problem. There are various solutions, all of which involve raising PropertyChanged only after you finish adding all properties.
If you don't need the old values, just create a list with the new values, replace the old values then raise the PropertyChanged event, eg:
private ObservebleCollection<String> _parameters = new ObservebleCollection<String>();
public ObservebleCollection<String> Parameters
{
get
{
return _parameters;
}
private set
{
_parameters=value;
PropertyChanged("Parameters");
}
public void CopyParameters()
{
var newParameters=currentRobot.GetParametersProvider()
.GetParameters()
.Select(p=>p.PropertyName);
Parameters=new ObservableCollection<string>(newParameters);
}
Unless you have code that modified Parameters one item at a time though, you could easily swap ObservableCollection for any other collection type, even a string[] array.
Another option is to subclass ObservableCollection to add support for AddRange, as shown in this SO question
I think i have some problems with logic right now.
I have used a blocking collection to make thread safe calls to other PCs. In general it looks like this:
public class MyClass
{
private BlockingCollection<workUnit> workUnits = new BlockingCollection<workUnit>();
public void EnQueue(workUnit item)
{
workUnits.Add(item);
}
private void DeQueue()
{
while (!stopFlag)
{
workUnit item = workUnits.Take();
DoLongRunningDBStuff(workUnit);
}
}
}
Now I want to visualize this to a user.
A user should see that
items are in Queue
item processing has started
result of processing (mainly passed/failed/exception)
And now I got some headache.
I was thinking to do the following:
Have a Grid to display the items to users.
If item is Enqueued add it to workunits and additionally to a list bound to the datagrid
If item is Dequeued (consumed) update the item in the list for the grid.
What makes the headache, is how to make this thread safe, and which parts are needed to be thread safe.
If I put something which takes time behind workUnit.Add I think it could be possible, that data gets mixed.
Would something like this be feasible?
If item is Enqueued add it to workunits and an additional BlockingCollection for UI
If item is Dequeued, make a tryget on 2. BlockingCollection and remove it there, update the status and attach it to second list again.
Would I need an additional lock aound 1 and 2? If so, wouldn't it block completly the add if waiting for Take?
Is there an easy solution or approach to visualize, what is going on?
I will try do it this way:
public class MyClass
{
private BlockingCollection<workUnit> workUnits = new BlockingCollection<workUnit>();
public void EnQueue(workUnit item)
{
workUnits.Add(item);
}
private void DeQueue()
{
while (!stopFlag)
{
workUnit item = workUnits.Take();
item.SetState("Processing Started");
try
{
DoLongRunningDBStuff(workUnit);
item.SetState("Processing Successful");
}
catch
{
item.SetState("Processing Failed");
}
}
}
}
in this example I would then make workItem.SetState(...) fire an event that will update UI for the particular item. However, because the event is raised in a non-UI thread, it will be the handler of the event (the form displaying the grid I would assume) that would need to post the update into the context of the UI thread (e.g. If you are using WinForms you would call the Invoke method of the control displaying the data).
In another (preferred) suggestion I would do the following (if you can use the TPL in .NET 4.0 and later):
public class MyClass
{
public Task EnQueue(workUnit item)
{
// Schedule the work on the thread pool.
// If you need limited concurrency here, there are schedulers to enable this.
return Task.Run(() => DoLongRunningDBStuff(item));
}
}
And if you use .NET 4.5 you would be able to use the await feature that would automatically synchronise the continuation of the task in the context of the UI thread. E.g. in the on the caller's side (assuming it is initiated on the UI thread) you would simply do the following:
private async void btnAddItem_Click(object sender, EventArgs e)
{
var item = new workUnit();
// TODO: Add item on UI here
try
{
await myClass.EnQueue(item);
// TODO: Update UI with success result here (no context synchronisation is needed here it is already in the UI context)
}
catch
{
// TODO: Update UI with error result here (no context synchronisation is needed here it is already in the UI context)
}
}
In both examples you do not even need any locking, you simply need to have the updates posted to the correct context (and in the last example that is not even explicitly needed, the compiler takes care of it for you)
What's the best way to thread work (methods) in c#?
For example:
Let's say I have a form and want to load data from db.
My form controls:
- dataGridView (to show data from DB),
- label (loading status) and
- button (start loading).
When I click the button my form is frozen until the task is done. Also the loading status does not change until task is done. I think async threading would be the answer?
So my question: what's the best way to handle this? I know there is a lot stuff about Threading, but what's the difference between them and how do you make it thread safe?
How do you solve this kind of problems?
Best Regards.
If using Windows Forms, you should look at BackrgroundWorker. More generally, it is often useful to use the ThreadPool class. And finally, it is worth to take a look at the new .NET 4's Parallel class.
There is no universal 'best' way to thread work. You just have to try different ways of doing things, I'm afraid.
I particularly like Jeremy D. Miller's continuation idea described at this page (scroll down to find the "continuations" section). It's really elegant and means writing very little boilerplate code.
Basically, when you call "ExecuteWithContinuation" with a Func argument, the function is executed asynchronously, then returns an action when it finishes. The action is then marshalled back onto your UI thread to act as a continuation. This allows you to quickly split your operations into two bits:
Perform long running operation that shouldn't block the UI
... when finished, update the UI on the UI thread
It takes a bit of getting used to, but it's pretty cool.
public class AsyncCommandExecutor : ICommandExecutor
{
private readonly SynchronizationContext m_context;
public AsyncCommandExecutor(SynchronizationContext context)
{
if (context == null) throw new ArgumentNullException("context");
m_context = context;
}
public void Execute(Action command)
{
ThreadPool.QueueUserWorkItem(o => command());
}
public void ExecuteWithContinuation(Func<Action> command)
{
ThreadPool.QueueUserWorkItem(o =>
{
var continuation = command();
m_context.Send(x => continuation(), null);
});
}
}
You'd then use it like this (forgive the formatting...)
public void DoSomethingThatTakesAgesAndNeedsToUpdateUiWhenFinished()
{
DisableUi();
m_commandExecutor.ExecuteWithContinuation(
() =>
{
// this is the long-running bit
ConnectToServer();
// This is the continuation that will be run
// on the UI thread
return () =>
{
EnableUi();
};
});
}
You can use this kind of pattern:-
private void RefreshButton_Click(object sender, EventArgs e)
{
MessageLabel.Text = "Working...";
RefreshButton.Enabled = false;
ThreadPool.QueueUserWorkItem(delegate(object state)
{
// do work here
// e.g.
object datasource = GetData();
this.Invoke((Action<object>)delegate(object obj)
{
// gridview should also be accessed in UI thread
// e.g.
MyGridView.DataSource = obj;
MessageLabel.Text = "Done.";
RefreshButton.Enabled = true;
}, datasource);
});
}
You cannot access your controls from the code that runs in the spun-off thread - the framework does not allow this, which explains the error you are getting.
You need to cache the data retrieved from the db in a non-forms object and populate your UI with data from that object after the background worker thread is done (and handle synchronization for access to that object).