Если вы используете Roboguice, вы можете использовать EventManager в Roboguice для передачи данных без использования Activity в качестве интерфейса. Это довольно чистый ИМО.
Если вы не используете Roboguice, вы можете использовать Otto также как шину событий: http://square.github.com/otto/
Обновление 20150909: вы также можете использовать Green Bus Robot Event Bus или даже RxJava. Зависит от вашего прецедента.
Для.NET 2.0, вот хороший бит кода, я записал, что это делает точно, что Вы хотите, и работы для любого свойства на Control
:
private delegate void SetControlPropertyThreadSafeDelegate(
Control control,
string propertyName,
object propertyValue);
public static void SetControlPropertyThreadSafe(
Control control,
string propertyName,
object propertyValue)
{
if (control.InvokeRequired)
{
control.Invoke(new SetControlPropertyThreadSafeDelegate
(SetControlPropertyThreadSafe),
new object[] { control, propertyName, propertyValue });
}
else
{
control.GetType().InvokeMember(
propertyName,
BindingFlags.SetProperty,
null,
control,
new object[] { propertyValue });
}
}
Вызов это как это:
// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
при использовании.NET 3.0 или выше Вы могли бы переписать вышеупомянутый метод как дополнительный метод Control
класс, который тогда упростит вызов до:
myLabel.SetPropertyThreadSafe("Text", status);
ОБНОВЛЕНИЕ 10.05.2010:
Для.NET 3.0 необходимо использовать этот код:
private delegate void SetPropertyThreadSafeDelegate<TResult>(
Control @this,
Expression<Func<TResult>> property,
TResult value);
public static void SetPropertyThreadSafe<TResult>(
this Control @this,
Expression<Func<TResult>> property,
TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member
as PropertyInfo;
if (propertyInfo == null ||
!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
@this.GetType().GetProperty(
propertyInfo.Name,
propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TResult>
(SetPropertyThreadSafe),
new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(
propertyInfo.Name,
BindingFlags.SetProperty,
null,
@this,
new object[] { value });
}
}
, который использует LINQ и лямбда-выражения для разрешения намного более чистого, более простого и более безопасного синтаксиса:
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
Не только имя свойства, теперь проверенное во время компиляции, тип свойства также, таким образом, невозможно (например), присвоить строковое значение булево свойству и следовательно вызвать исключение на этапе выполнения.
, К сожалению, это не мешает никому делать глупые вещи, такие как передача в другом Control
свойство и значение, таким образом, следующее счастливо скомпилирует:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
Следовательно я добавил проверки на этапе выполнения, чтобы гарантировать, что переданный - в свойстве действительно на самом деле принадлежит Control
что то, что метод был обращенным. Не прекрасный, но еще намного лучше, чем.NET 2,0 версии.
, Если у кого-либо есть дальнейшие предложения о том, как улучшить этот код для безопасности времени компиляции, прокомментируйте!
самый простой путем является анонимный метод, переданный в Label.Invoke
:
// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
// Running on the UI thread
form.Label.Text = newText;
});
// Back on the worker thread
Уведомление, что Invoke
выполнение блоков, пока это не завершается - это - синхронный код. Вопрос не спрашивает об асинхронном коде, но существует много из содержание на Переполнении стека о написании асинхронного кода, когда Вы хотите узнать об этом.
Простое решение состоит в том, чтобы использовать Control.Invoke
.
void DoSomething()
{
if (InvokeRequired) {
Invoke(new MethodInvoker(updateGUI));
} else {
// Do Something
updateGUI();
}
}
void updateGUI() {
// update gui here
}
Необходимо будет удостовериться, что обновление происходит на корректном потоке; поток UI.
, Чтобы сделать это, необходимо будет Вызвать обработчик событий вместо того, чтобы назвать его непосредственно.
можно сделать это путем генерирования события как это:
(Код введен здесь из моей головы, таким образом, я не проверил на правильный синтаксис, и т.д., но это должно получить Вас движение.)
if( MyEvent != null )
{
Delegate[] eventHandlers = MyEvent.GetInvocationList();
foreach( Delegate d in eventHandlers )
{
// Check whether the target of the delegate implements
// ISynchronizeInvoke (Winforms controls do), and see
// if a context-switch is required.
ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;
if( target != null && target.InvokeRequired )
{
target.Invoke (d, ... );
}
else
{
d.DynamicInvoke ( ... );
}
}
}
Примечание, что код выше не будет работать над проектами WPF, начиная со средств управления WPF, не реализует эти ISynchronizeInvoke
интерфейс.
, Чтобы удостовериться, что код выше работ с Windows Forms и WPF и всеми другими платформами, можно взглянуть на AsyncOperation
, AsyncOperationManager
и SynchronizationContext
классы.
, Чтобы легко сгенерировать события этот путь, я создал дополнительный метод, который позволяет мне упрощать генерирование события, просто звоня:
MyEvent.Raise(this, EventArgs.Empty);
, Конечно, можно также использовать класс BackGroundWorker, который абстрагирует этот вопрос для Вас.
Это - классический способ, которым необходимо сделать это:
using System;
using System.Windows.Forms;
using System.Threading;
namespace Test
{
public partial class UIThread : Form
{
Worker worker;
Thread workerThread;
public UIThread()
{
InitializeComponent();
worker = new Worker();
worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
workerThread = new Thread(new ThreadStart(worker.StartWork));
workerThread.Start();
}
private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
{
// Cross thread - so you don't get the cross-threading exception
if (this.InvokeRequired)
{
this.BeginInvoke((MethodInvoker)delegate
{
OnWorkerProgressChanged(sender, e);
});
return;
}
// Change control
this.label1.Text = e.Progress;
}
}
public class Worker
{
public event EventHandler<ProgressChangedArgs> ProgressChanged;
protected void OnProgressChanged(ProgressChangedArgs e)
{
if(ProgressChanged!=null)
{
ProgressChanged(this,e);
}
}
public void StartWork()
{
Thread.Sleep(100);
OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
Thread.Sleep(100);
}
}
public class ProgressChangedArgs : EventArgs
{
public string Progress {get;private set;}
public ProgressChangedArgs(string progress)
{
Progress = progress;
}
}
}
Ваш рабочий поток имеет событие. Ваш поток UI начинается другой поток, чтобы сделать работу и поднимает трубку то событие рабочего, таким образом, можно отобразить состояние рабочего потока.
Тогда в UI необходимо пересечь потоки для изменения фактического управления... как маркировка или индикатор выполнения.
Необходимо будет Вызвать метод на поток GUI. Можно сделать это путем вызова Управления. Вызвать.
, Например:
delegate void UpdateLabelDelegate (string message);
void UpdateLabel (string message)
{
if (InvokeRequired)
{
Invoke (new UpdateLabelDelegate (UpdateLabel), message);
return;
}
MyLabelControl.Text = message;
}
Для многих целей это так просто:
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
«serviceGUI ()» - это метод уровня графического интерфейса пользователя в форме (this), который может изменять столько элементов управления, сколько вы хотите. Вызовите updateGUI () из другого потока. Параметры могут быть добавлены для передачи значений или (возможно, быстрее) использовать переменные области класса с блокировками по мере необходимости, если существует какая-либо вероятность конфликта между потоками, обращающимися к ним, что может вызвать нестабильность. Используйте BeginInvoke вместо Invoke, если поток, не связанный с графическим интерфейсом пользователя, критичен по времени (помня о предупреждении Брайана Гидеона).
Из-за тривиальности сценария у меня был бы опрос потока пользовательского интерфейса для определения статуса. Я думаю, вы обнаружите, что это может быть довольно элегантно.
public class MyForm : Form
{
private volatile string m_Text = "";
private System.Timers.Timer m_Timer;
private MyForm()
{
m_Timer = new System.Timers.Timer();
m_Timer.SynchronizingObject = this;
m_Timer.Interval = 1000;
m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
m_Timer.Start();
var thread = new Thread(WorkerThread);
thread.Start();
}
private void WorkerThread()
{
while (...)
{
// Periodically publish progress information.
m_Text = "Still working...";
}
}
}
Этот подход позволяет избежать операции маршалинга, необходимой при использовании методов ISynchronizeInvoke.Invoke
и ISynchronizeInvoke.BeginInvoke
. Нет ничего плохого в использовании техники маршалинга, но есть несколько предостережений, о которых вам нужно знать.
BeginInvoke
слишком часто, иначе это может вызвать перегрузку насоса сообщений. Invoke
в рабочем потоке является вызовом блокировки. Это временно остановит работу, выполняемую в этом потоке. Стратегия, которую я предлагаю в этом ответе, меняет коммуникационные роли потоков. Вместо того, чтобы рабочий поток проталкивал данные, поток пользовательского интерфейса опрашивает их. Это общий шаблон, используемый во многих сценариях. Поскольку все, что вам нужно, это отображать информацию о ходе выполнения из рабочего потока, я думаю, вы обнаружите, что это решение является отличной альтернативой решению для маршалинга. Он имеет следующие преимущества.
Control.Invoke
или Control.BeginInvoke
, который их тесно связывает. Поточный код часто содержит ошибки, и его всегда трудно тестировать. Вам не нужно писать многопоточный код для обновления пользовательского интерфейса из фоновой задачи. Просто используйте класс BackgroundWorker для запуска задачи и его метод ReportProgress для обновления пользовательского интерфейса. Обычно вы просто сообщаете о проценте завершения, но есть и другая перегрузка, включающая объект состояния. Вот пример, который просто сообщает о строковом объекте:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "A");
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "B");
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "C");
}
private void backgroundWorker1_ProgressChanged(
object sender,
ProgressChangedEventArgs e)
{
label1.Text = e.UserState.ToString();
}
Это нормально, если вы всегда хотите обновлять одно и то же поле. Если вам нужно сделать более сложные обновления, вы можете определить класс для представления состояния пользовательского интерфейса и передать его методу ReportProgress.
И последнее: обязательно установите флаг WorkerReportsProgress
, иначе метод ReportProgress
будет полностью проигнорирован.
Метод расширения "запустил и забыл" для .NET 3.5+
using System;
using System.Windows.Forms;
public static class ControlExtensions
{
/// <summary>
/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
/// </summary>
/// <param name="control"></param>
/// <param name="code"></param>
public static void UIThread(this Control @this, Action code)
{
if (@this.InvokeRequired)
{
@this.BeginInvoke(code);
}
else
{
code.Invoke();
}
}
}
Его можно вызвать с помощью следующей строки кода:
this.UIThread(() => this.myLabel.Text = "Text Goes Here");