.net - ¿Dónde obtengo un CollectionView seguro para subprocesos?
wpf multithreading (11)
Aquí hay una versión de VB que hice después de googlear y ligeras modificaciones. Funciona para mi.
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Imports System.ComponentModel
Imports System.Reflection
Imports System.Windows.Threading
''from: http://stackoverflow.com/questions/2137769/where-do-i-get-a-thread-safe-collectionview
Public Class ThreadSafeObservableCollection(Of T)
Inherits ObservableCollection(Of T)
''from: http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/listcollectionviewcollectionview-doesnt-support-notifycollectionchanged-with-multiple-items.aspx
Protected Overrides Sub OnCollectionChanged(ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs)
Dim doit As Boolean = False
doit = (e.NewItems IsNot Nothing) AndAlso (e.NewItems.Count > 0)
doit = doit OrElse ((e.OldItems IsNot Nothing) AndAlso (e.OldItems.Count > 0))
If (doit) Then
Dim handler As NotifyCollectionChangedEventHandler = GetType(ObservableCollection(Of T)).GetField("CollectionChanged", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(Me)
If (handler Is Nothing) Then
Return
End If
For Each invocation As NotifyCollectionChangedEventHandler In handler.GetInvocationList
Dim obj As DispatcherObject = invocation.Target
If (obj IsNot Nothing) Then
Dim disp As Dispatcher = obj.Dispatcher
If (disp IsNot Nothing AndAlso Not (disp.CheckAccess())) Then
disp.BeginInvoke(
Sub()
invocation.Invoke(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
End Sub, DispatcherPriority.DataBind)
Continue For
End If
End If
invocation.Invoke(Me, e)
Next
End If
End Sub
End Class
Al actualizar una colección de objetos comerciales en un hilo de fondo aparece este mensaje de error:
Este tipo de CollectionView no admite cambios en su SourceCollection de un hilo diferente del hilo Dispatcher.
Ok, eso tiene sentido. Pero también plantea la pregunta, ¿qué versión de CollectionView admite múltiples hilos y cómo hago que mis objetos la usen?
Encontré uno.
public class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
var eh = CollectionChanged;
if (eh != null)
{
Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
let dpo = nh.Target as DispatcherObject
where dpo != null
select dpo.Dispatcher).FirstOrDefault();
if (dispatcher != null && dispatcher.CheckAccess() == false)
{
dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
}
else
{
foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
nh.Invoke(this, e);
}
}
}
}
http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx
Lo siento, no puedo agregar un comentario pero todo esto está mal.
ObservableCollection no es seguro para subprocesos. No solo por los problemas de este despachador, sino porque no es seguro para subprocesos (desde msdn):
Cualquier miembro público estático (compartido en Visual Basic) de este tipo es seguro para subprocesos. No se garantiza que ningún miembro de instancia sea seguro para subprocesos.
Mira aquí http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx
También hay un problema al llamar a BeginInvoke con una acción de "Restablecimiento". "Restablecer" es la única acción donde el manejador debe mirar la colección en sí. Si comienzaInvoque un "Restablecer" y luego Inmediato Invoque un par de acciones "Agregar" que el controlador aceptará un "Restablecer" con la colección ya actualizada y el siguiente "Agregar" creará un desastre.
Aquí está mi implementación que funciona. En realidad, estoy pensando en eliminar BeginInvoke en absoluto:
Colección observable de ejecución rápida y segura para hilos
Lo siguiente es una mejora en la implementación encontrada por Jonathan. En primer lugar, ejecuta cada controlador de eventos en el despachador asociado, en lugar de asumir que todos están en el mismo despachador (UI). En segundo lugar, usa BeginInvoke para permitir que el procesamiento continúe mientras esperamos que el despachador esté disponible. Esto hace que la solución sea mucho más rápida en situaciones donde el hilo de fondo está haciendo muchas actualizaciones con procesamiento entre cada una. Quizás lo más importante es que supera los problemas causados por el bloqueo mientras espera la Invocación (los bloqueos pueden ocurrir, por ejemplo, al usar WCF con ConcurrencyMode.Single).
public class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
if (CollectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
{
DispatcherObject dispObj = nh.Target as DispatcherObject;
if (dispObj != null)
{
Dispatcher dispatcher = dispObj.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
nh.Invoke(this, e);
}
}
}
Debido a que estamos utilizando BeginInvoke, es posible que el cambio notificado se deshaga antes de que se llame al controlador. Esto normalmente daría como resultado un "Índice estaba fuera de rango". se lanza una excepción cuando los argumentos del evento se comparan con el nuevo estado (alterado) de la lista. Para evitar esto, todos los eventos retrasados se reemplazan con eventos de reinicio. Esto podría causar un redibujado excesivo en algunos casos.
Ninguno de ellos, solo use Dispatcher.BeginInvoke
Pequeño error en la versión VB. Simplemente reemplace:
Dim obj As DispatcherObject = invocation.Target
Por
Dim obj As DispatcherObject = TryCast(invocation.Target, DispatcherObject)
Prueba esto:
this.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
() =>
{
//Code
}));
Si desea actualizar el control de interfaz de usuario de WPF periódicamente y, al mismo tiempo, usar la interfaz de usuario, puede usar DispatcherTimer .
XAML
<Grid>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Name="dgDownloads" VerticalAlignment="Top" Width="548" />
<Label Content="" Height="28" HorizontalAlignment="Left" Margin="0,221,0,0" Name="lblFileCouner" VerticalAlignment="Top" Width="173" />
</Grid>
DO#
public partial class DownloadStats : Window
{
private MainWindow _parent;
DispatcherTimer timer = new DispatcherTimer();
ObservableCollection<FileView> fileViewList = new ObservableCollection<FileView>();
public DownloadStats(MainWindow parent)
{
InitializeComponent();
_parent = parent;
Owner = parent;
timer.Interval = new TimeSpan(0, 0, 1);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
dgDownloads.ItemsSource = null;
fileViewList.Clear();
if (_parent.contentManagerWorkArea.Count > 0)
{
foreach (var item in _parent.contentManagerWorkArea)
{
FileView nf = item.Value.FileView;
fileViewList.Add(nf);
}
}
if (fileViewList.Count > 0)
{
lblFileCouner.Content = fileViewList.Count;
dgDownloads.ItemsSource = fileViewList;
}
}
}
También puede consultar: BindingOperations.EnableCollectionSynchronization
.
Consulte Actualización a .NET 4.5: Un control de elementos no es coherente con su fuente de elementos
Utilizar:
System.Windows.Application.Current.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
(Action)delegate()
{
// Your Action Code
});
This publicación de Bea Stollnitz explica ese mensaje de error y por qué está redactado tal como es.
EDITAR: Del blog de Bea
Desafortunadamente, este código da como resultado una excepción: "NotSupportedException: este tipo de CollectionView no admite cambios en su SourceCollection desde un hilo diferente al de Dispatcher". Entiendo que este mensaje de error lleva a las personas a pensar que, si CollectionView están el uso no admite cambios cruzados, entonces tienen que encontrar el que sí lo haga. Bueno, este mensaje de error es un poco engañoso: ninguno de los CollectionViews que brindamos de fábrica admite cambios en la recolección de hilos cruzados. Y no, desafortunadamente no podemos arreglar el mensaje de error en este momento, estamos muy bloqueados.