I'm using MVVM, and in my viewmodel I start a thread (it is a server application, so it is a connection thread), and I'm looking for an appropriate way (or ways (!)) to notify the UI about what's happening on another thread. The way I want to do it, to have a textbox of some sort for logs (lines stored in an ObservableCollection probably), and each time something happens on the connection thread, I would want a new line added to the textbox. Here's how I set the command (the method starts a thread which is listening for connections):
我正在使用MVVM,在我的viewmodel中,我启动了一个线程(它是一个服务器应用程序,所以它是一个连接线程),我正在寻找一个合适的方法(或者ways(!))来通知UI在另一个线程上发生了什么。我希望使用的方式是,为日志(可能存储在一个ObservableCollection中的行)提供某种类型的文本框,并且每当连接线程发生什么事情时,我希望向文本框中添加新的行。下面是我如何设置命令(该方法启动一个正在监听连接的线程):
public ViewModel()
{
StartCommand = new RelayCommand(PacketHandler.Start);
}
PacketHandler class:
PacketHandler类:
public static void Start()
{
var connectionThread = new Thread(StartListening);
connectionThread.IsBackground = true;
connectionThread.Start();
}
private static void StartListening()
{
if (!isInitialized) Initialize();
try
{
listener.Start();
while (true)
{
client = listener.AcceptTcpClient();
// some kind of logging here which reaches the ui immediately
var protocol = new Protocol(client);
var thread = new Thread(protocol.StartCommunicating) { IsBackground = true };
thread.Start();
connectedThreads.Add(thread);
}
}
catch (Exception)
{
// temp
MessageBox.Show("Error in PacketHandler class");
}
}
I'm looking for possible solutions, preferably the best. I'm a beginner programmer, so I may not comprehend the most complex solutions, please bear in mind this, too. NOTE: I read about events, observer pattern, and a few other things as possible solutions, only I don't know which (and of course: how) to use them properly. Thanks in advance!
我在寻找可能的解决方案,最好是最好的。我是一个初学者程序员,所以我可能不理解最复杂的解决方案,请记住这一点。注意:我阅读了一些事件、观察者模式和一些其他可能的解决方案,但是我不知道如何正确地使用它们。提前谢谢!
3 个解决方案
#1
2
One more similar working example
还有一个类似的工作示例
View
视图
<Window x:Class="MultipleDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ScrollViewer MaxHeight="100">
<ItemsControl ItemsSource="{Binding ServerLog}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Window>
View CodeBehind
查看后台代码
using System.Windows;
namespace MultipleDataGrid
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
Your ServerThread
你ServerThread
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows;
using System.Windows.Data;
namespace MultipleDataGrid
{
public class Something
{
public static void Start()
{
var connectionThread = new Thread(StartListening);
Log = new ObservableCollection<string>();
BindingOperations.EnableCollectionSynchronization(Log, _lock);//For Thread Safety
connectionThread.IsBackground = true;
connectionThread.Start();
}
public static ObservableCollection<string> Log { get; private set; }
private static readonly object _lock = new object();
private static void StartListening()
{
try
{
int i = 0;
while (i <= 100)
{
Log.Add("Something happened " + i);
Thread.Sleep(1000);
i++;
}
}
catch (Exception)
{
// temp
MessageBox.Show("Error in PacketHandler class");
}
}
}
}
And finally the ViewModel
最后ViewModel
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace MultipleDataGrid
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> ServerLog { get; private set; }
public ViewModel()
{
Something.Start();
Something.Log.CollectionChanged += (s, e) =>
{
ServerLog = Something.Log;
RaisePropertyChanged("ServerLog");
};
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
pc(this, new PropertyChangedEventArgs(propName));
}
}
}
#2
3
I am going to introduce you to BlockingCollection<T>
which is a thread-safe collection class that provides the following:
我将向您介绍BlockingCollection
- An implementation of the producer/consumer pattern;
BlockingCollection<T>
is a wrapper for theIProducerConsumerCollection<T>
interface. -
生产者/消费者模式的实现;BlockingCollection
是IProducerConsumerCollection 接口的包装器。 - Concurrent addition and removal of items from multiple threads with the Add and Take methods.
- 使用Add和Take方法从多个线程并发地添加和删除项目。
- A bounded collection that blocks Add and Take operations when the collection is full or empty.
- 一个有限制的集合,当集合是满的或空的时候,它会阻塞添加和执行操作。
- Cancellation of Add or Take operations by using a CancellationToken object in the TryAdd or TryTake method.
- 通过在TryAdd或TryTake方法中使用一个cancationtoken对象取消添加或获取操作。
here is a simple example for you
这里有一个简单的例子
public static void Start()
{
var connectionThread = new Thread(StartListening);
connectionThread.IsBackground = true;
connectionThread.Start();
ThreadPool.QueueUserWorkItem(Logger); //start logger thread
}
//thread safe data collection, can be modified from multiple threads without threading issues
static BlockingCollection<string> logData = new BlockingCollection<string>();
public ObservableCollection<string> Logs { get; set; } // to bind to the UI
private void Logger(object state)
{
//collect everything from the logData, this loop will not terminate until `logData.CompleteAdding()` is called
foreach (string item in logData.GetConsumingEnumerable())
{
//add the item to the UI bound ObservableCollection<string>
Dispatcher.Invoke(() => Logs.Add(item));
}
}
private static void StartListening()
{
if (!isInitialized) Initialize();
try
{
listener.Start();
while (true)
{
client = listener.AcceptTcpClient();
// some kind of logging here which reaches the ui immediately
logData.TryAdd("log"); //adding a log entry to the logData, completely thread safe
var protocol = new Protocol(client);
var thread = new Thread(protocol.StartCommunicating) { IsBackground = true };
thread.Start();
connectedThreads.Add(thread);
}
}
catch (Exception)
{
// temp
MessageBox.Show("Error in PacketHandler class");
}
}
using this approach you can also have multiple threads adding log data without threading issues.
使用这种方法,您还可以让多个线程添加日志数据,而不存在线程问题。
for more info on BlockingCollection<T>
refer http://msdn.microsoft.com/en-us/library/dd267312
有关BlockingCollection
Update
更新
view model class
视图模型类
public class ViewModel
{
private Dispatcher Dispatcher;
public ViewModel()
{
StartCommand = new RelayCommand(PacketHandler.Start);
// dispatcher is required for UI updates
// remove this line and the variable if there is one
// also assuming this constructor will be called from UI (main) thread
Dispatcher = Dispatcher.CurrentDispatcher;
ThreadPool.QueueUserWorkItem(Logger); //start logger thread
}
public ObservableCollection<string> Logs { get; set; } // to bind to the UI
private void Logger(object state)
{
//collect everything from the LogData, this loop will not terminate until `CompleteAdding()` is called on LogData
foreach (string item in PacketHandler.LogData.GetConsumingEnumerable())
{
//add the item to the UI bound ObservableCollection<string>
Dispatcher.Invoke(() => Logs.Add(item));
}
}
}
and packet handler class
和包处理程序类
public class PacketHandler
{
public static BlockingCollection<string> LogData = new BlockingCollection<string>();
private static void StartListening()
{
if (!isInitialized) Initialize();
try
{
listener.Start();
while (true)
{
client = listener.AcceptTcpClient();
// some kind of logging here which reaches the ui immediately
LogData.TryAdd("log"); //adding a log entry to the logData, completely thread safe
var protocol = new Protocol(client);
var thread = new Thread(protocol.StartCommunicating) { IsBackground = true };
thread.Start();
connectedThreads.Add(thread);
}
}
catch (Exception)
{
// temp
MessageBox.Show("Error in PacketHandler class");
}
}
}
this will work for your case
这对你的案子有用
#3
2
If you are using MVVM and want to create a thread to perform a given task and report back some progress to the UI without cross-threaded exceptions, you can use SOLID
principles to create a MyWorker
class that looks like this...
如果您正在使用MVVM,并希望创建一个线程来执行给定的任务,并在没有跨线程异常的情况下向UI报告一些进展,那么您可以使用可靠的原则来创建一个类似于…
public class MyWorker : IObservable<string>, IDisposable
{
private Task _task;
private IObserver<string> _observer;
public IDisposable Subscribe(IObserver<string> observer)
{
_observer = observer;
return this;
}
public void StartWork()
{
_task = new Task(() =>
{
while (true)
{
// background work goes here
Thread.Sleep(2000);
if (_observer != null)
{
string status = DateTime.Now.ToString("G");
_observer.OnNext(status);
}
}
});
_task.ContinueWith(r =>
{
if (_observer != null)
{
_observer.OnCompleted();
}
});
_task.Start();
}
public void Dispose()
{
if (_task != null)
{
_task.Dispose();
_task = null;
}
}
}
It is a light-weight encapsulation of the background task. The class simply creates a Task and reports back the time every two seconds. It uses the IObservable pattern, which provides push notifications. It is documented here http://msdn.microsoft.com/en-us/library/dd990377(v=vs.110).aspx
它是后台任务的轻量级封装。这个类简单地创建一个任务,并每两秒报告一次时间。它使用IObservable模式,提供推送通知。本文提供了http://msdn.microsoft.com/en-us/library/dd990377(v=vs.110).aspx文件
A simple ViewModel that instantiates this class looks like this...
一个实例化这个类的简单视图模型是这样的…
public class ViewModel : INotifyPropertyChanged, IObserver<string>
{
readonly ListCollectionView _listCollectionView;
public ViewModel()
{
LogEntries = new ObservableCollection<string>();
_listCollectionView = CollectionViewSource.GetDefaultView(LogEntries) as ListCollectionView;
if (_listCollectionView != null)
{
MyWorker worker = new MyWorker();
worker.Subscribe(this);
worker.StartWork();
}
}
public ObservableCollection<string> LogEntries { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void OnNext(string logEntry)
{
_listCollectionView.Dispatcher.InvokeAsync(() => LogEntries.Add(logEntry));
}
public void OnCompleted()
{
// clean up goes here
}
public void OnError(Exception error)
{
// error handling goes here
}
}
The only difference between this VM and your VM is that this one implements the IObserver pattern, which provides a mechanism for receiving push-based notifications. The docs are here http://msdn.microsoft.com/en-us/library/dd783449(v=vs.110).aspx
这个VM和您的VM之间的惟一区别是,这个实现了IObserver模式,该模式提供了一种接收基于推送的通知的机制。文档在这里http://msdn.microsoft.com/en-us/library/dd783449(v=vs.110).aspx
Because it's simple, the VM starts the thread in the constructor. In your case, you would start the thread in the Execute
delegate of your StartCommand. The VM above works with a collection of strings, hence the need for a dispatcher. Fortunately the dispatcher is provided out-of-the-box by the ListCollectionView
class. http://msdn.microsoft.com/en-us/library/system.windows.data.listcollectionview.aspx If instead, you are updating a string property then the dispatcher is not needed because the binding engine does the marshalling for you.
因为很简单,VM在构造函数中启动线程。在您的示例中,您将在StartCommand的Execute委托中启动线程。上面的VM使用一组字符串,因此需要一个分派器。幸运的是,ListCollectionView类提供了分派器。http://msdn.microsoft.com/en-us/library/system.windows.data.listcollectionview.aspx相反,如果您正在更新一个字符串属性,则不需要分派器,因为绑定引擎为您进行封送。
With these two classes, a small application can be made with this Xaml...
有了这两个类,就可以用这个Xaml来创建一个小应用程序。
<Grid>
<ListBox ItemsSource="{Binding LogEntries}"/>
</Grid>
When the application is run, the ListBox will be updated every two seconds with no threading conflicts while maintaining a responsive UI.
当应用程序运行时,列表框将每两秒钟更新一次,同时保持响应UI,不会出现线程冲突。
Note: I built the application under .NET 4.5, and the MINIMUM version is .NET 4.0. It will work without Rx. If you decide to go with the full RX, you can take advantage of the ObserveOn method which gives further streamlining to multi-threaded applications. You can use the NuGet manager from within Visual Studio to install the full Reactive Extensions.
注意:我在。net 4.5中构建了这个应用程序,最小版本是。net 4.0。没有Rx也能工作。如果您决定使用完整的RX,您可以利用ObserveOn方法进一步简化多线程应用程序。您可以使用Visual Studio中的NuGet管理器来安装完整的反应性扩展。
#1
2
One more similar working example
还有一个类似的工作示例
View
视图
<Window x:Class="MultipleDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ScrollViewer MaxHeight="100">
<ItemsControl ItemsSource="{Binding ServerLog}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Window>
View CodeBehind
查看后台代码
using System.Windows;
namespace MultipleDataGrid
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
Your ServerThread
你ServerThread
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows;
using System.Windows.Data;
namespace MultipleDataGrid
{
public class Something
{
public static void Start()
{
var connectionThread = new Thread(StartListening);
Log = new ObservableCollection<string>();
BindingOperations.EnableCollectionSynchronization(Log, _lock);//For Thread Safety
connectionThread.IsBackground = true;
connectionThread.Start();
}
public static ObservableCollection<string> Log { get; private set; }
private static readonly object _lock = new object();
private static void StartListening()
{
try
{
int i = 0;
while (i <= 100)
{
Log.Add("Something happened " + i);
Thread.Sleep(1000);
i++;
}
}
catch (Exception)
{
// temp
MessageBox.Show("Error in PacketHandler class");
}
}
}
}
And finally the ViewModel
最后ViewModel
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace MultipleDataGrid
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> ServerLog { get; private set; }
public ViewModel()
{
Something.Start();
Something.Log.CollectionChanged += (s, e) =>
{
ServerLog = Something.Log;
RaisePropertyChanged("ServerLog");
};
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
pc(this, new PropertyChangedEventArgs(propName));
}
}
}
#2
3
I am going to introduce you to BlockingCollection<T>
which is a thread-safe collection class that provides the following:
我将向您介绍BlockingCollection
- An implementation of the producer/consumer pattern;
BlockingCollection<T>
is a wrapper for theIProducerConsumerCollection<T>
interface. -
生产者/消费者模式的实现;BlockingCollection
是IProducerConsumerCollection 接口的包装器。 - Concurrent addition and removal of items from multiple threads with the Add and Take methods.
- 使用Add和Take方法从多个线程并发地添加和删除项目。
- A bounded collection that blocks Add and Take operations when the collection is full or empty.
- 一个有限制的集合,当集合是满的或空的时候,它会阻塞添加和执行操作。
- Cancellation of Add or Take operations by using a CancellationToken object in the TryAdd or TryTake method.
- 通过在TryAdd或TryTake方法中使用一个cancationtoken对象取消添加或获取操作。
here is a simple example for you
这里有一个简单的例子
public static void Start()
{
var connectionThread = new Thread(StartListening);
connectionThread.IsBackground = true;
connectionThread.Start();
ThreadPool.QueueUserWorkItem(Logger); //start logger thread
}
//thread safe data collection, can be modified from multiple threads without threading issues
static BlockingCollection<string> logData = new BlockingCollection<string>();
public ObservableCollection<string> Logs { get; set; } // to bind to the UI
private void Logger(object state)
{
//collect everything from the logData, this loop will not terminate until `logData.CompleteAdding()` is called
foreach (string item in logData.GetConsumingEnumerable())
{
//add the item to the UI bound ObservableCollection<string>
Dispatcher.Invoke(() => Logs.Add(item));
}
}
private static void StartListening()
{
if (!isInitialized) Initialize();
try
{
listener.Start();
while (true)
{
client = listener.AcceptTcpClient();
// some kind of logging here which reaches the ui immediately
logData.TryAdd("log"); //adding a log entry to the logData, completely thread safe
var protocol = new Protocol(client);
var thread = new Thread(protocol.StartCommunicating) { IsBackground = true };
thread.Start();
connectedThreads.Add(thread);
}
}
catch (Exception)
{
// temp
MessageBox.Show("Error in PacketHandler class");
}
}
using this approach you can also have multiple threads adding log data without threading issues.
使用这种方法,您还可以让多个线程添加日志数据,而不存在线程问题。
for more info on BlockingCollection<T>
refer http://msdn.microsoft.com/en-us/library/dd267312
有关BlockingCollection
Update
更新
view model class
视图模型类
public class ViewModel
{
private Dispatcher Dispatcher;
public ViewModel()
{
StartCommand = new RelayCommand(PacketHandler.Start);
// dispatcher is required for UI updates
// remove this line and the variable if there is one
// also assuming this constructor will be called from UI (main) thread
Dispatcher = Dispatcher.CurrentDispatcher;
ThreadPool.QueueUserWorkItem(Logger); //start logger thread
}
public ObservableCollection<string> Logs { get; set; } // to bind to the UI
private void Logger(object state)
{
//collect everything from the LogData, this loop will not terminate until `CompleteAdding()` is called on LogData
foreach (string item in PacketHandler.LogData.GetConsumingEnumerable())
{
//add the item to the UI bound ObservableCollection<string>
Dispatcher.Invoke(() => Logs.Add(item));
}
}
}
and packet handler class
和包处理程序类
public class PacketHandler
{
public static BlockingCollection<string> LogData = new BlockingCollection<string>();
private static void StartListening()
{
if (!isInitialized) Initialize();
try
{
listener.Start();
while (true)
{
client = listener.AcceptTcpClient();
// some kind of logging here which reaches the ui immediately
LogData.TryAdd("log"); //adding a log entry to the logData, completely thread safe
var protocol = new Protocol(client);
var thread = new Thread(protocol.StartCommunicating) { IsBackground = true };
thread.Start();
connectedThreads.Add(thread);
}
}
catch (Exception)
{
// temp
MessageBox.Show("Error in PacketHandler class");
}
}
}
this will work for your case
这对你的案子有用
#3
2
If you are using MVVM and want to create a thread to perform a given task and report back some progress to the UI without cross-threaded exceptions, you can use SOLID
principles to create a MyWorker
class that looks like this...
如果您正在使用MVVM,并希望创建一个线程来执行给定的任务,并在没有跨线程异常的情况下向UI报告一些进展,那么您可以使用可靠的原则来创建一个类似于…
public class MyWorker : IObservable<string>, IDisposable
{
private Task _task;
private IObserver<string> _observer;
public IDisposable Subscribe(IObserver<string> observer)
{
_observer = observer;
return this;
}
public void StartWork()
{
_task = new Task(() =>
{
while (true)
{
// background work goes here
Thread.Sleep(2000);
if (_observer != null)
{
string status = DateTime.Now.ToString("G");
_observer.OnNext(status);
}
}
});
_task.ContinueWith(r =>
{
if (_observer != null)
{
_observer.OnCompleted();
}
});
_task.Start();
}
public void Dispose()
{
if (_task != null)
{
_task.Dispose();
_task = null;
}
}
}
It is a light-weight encapsulation of the background task. The class simply creates a Task and reports back the time every two seconds. It uses the IObservable pattern, which provides push notifications. It is documented here http://msdn.microsoft.com/en-us/library/dd990377(v=vs.110).aspx
它是后台任务的轻量级封装。这个类简单地创建一个任务,并每两秒报告一次时间。它使用IObservable模式,提供推送通知。本文提供了http://msdn.microsoft.com/en-us/library/dd990377(v=vs.110).aspx文件
A simple ViewModel that instantiates this class looks like this...
一个实例化这个类的简单视图模型是这样的…
public class ViewModel : INotifyPropertyChanged, IObserver<string>
{
readonly ListCollectionView _listCollectionView;
public ViewModel()
{
LogEntries = new ObservableCollection<string>();
_listCollectionView = CollectionViewSource.GetDefaultView(LogEntries) as ListCollectionView;
if (_listCollectionView != null)
{
MyWorker worker = new MyWorker();
worker.Subscribe(this);
worker.StartWork();
}
}
public ObservableCollection<string> LogEntries { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void OnNext(string logEntry)
{
_listCollectionView.Dispatcher.InvokeAsync(() => LogEntries.Add(logEntry));
}
public void OnCompleted()
{
// clean up goes here
}
public void OnError(Exception error)
{
// error handling goes here
}
}
The only difference between this VM and your VM is that this one implements the IObserver pattern, which provides a mechanism for receiving push-based notifications. The docs are here http://msdn.microsoft.com/en-us/library/dd783449(v=vs.110).aspx
这个VM和您的VM之间的惟一区别是,这个实现了IObserver模式,该模式提供了一种接收基于推送的通知的机制。文档在这里http://msdn.microsoft.com/en-us/library/dd783449(v=vs.110).aspx
Because it's simple, the VM starts the thread in the constructor. In your case, you would start the thread in the Execute
delegate of your StartCommand. The VM above works with a collection of strings, hence the need for a dispatcher. Fortunately the dispatcher is provided out-of-the-box by the ListCollectionView
class. http://msdn.microsoft.com/en-us/library/system.windows.data.listcollectionview.aspx If instead, you are updating a string property then the dispatcher is not needed because the binding engine does the marshalling for you.
因为很简单,VM在构造函数中启动线程。在您的示例中,您将在StartCommand的Execute委托中启动线程。上面的VM使用一组字符串,因此需要一个分派器。幸运的是,ListCollectionView类提供了分派器。http://msdn.microsoft.com/en-us/library/system.windows.data.listcollectionview.aspx相反,如果您正在更新一个字符串属性,则不需要分派器,因为绑定引擎为您进行封送。
With these two classes, a small application can be made with this Xaml...
有了这两个类,就可以用这个Xaml来创建一个小应用程序。
<Grid>
<ListBox ItemsSource="{Binding LogEntries}"/>
</Grid>
When the application is run, the ListBox will be updated every two seconds with no threading conflicts while maintaining a responsive UI.
当应用程序运行时,列表框将每两秒钟更新一次,同时保持响应UI,不会出现线程冲突。
Note: I built the application under .NET 4.5, and the MINIMUM version is .NET 4.0. It will work without Rx. If you decide to go with the full RX, you can take advantage of the ObserveOn method which gives further streamlining to multi-threaded applications. You can use the NuGet manager from within Visual Studio to install the full Reactive Extensions.
注意:我在。net 4.5中构建了这个应用程序,最小版本是。net 4.0。没有Rx也能工作。如果您决定使用完整的RX,您可以利用ObserveOn方法进一步简化多线程应用程序。您可以使用Visual Studio中的NuGet管理器来安装完整的反应性扩展。