如何在松散耦合的应用程序中将状态信息传递给GUI

时间:2012-12-27 17:05:50

标签: c# logging

我第一次尝试使用依赖注入来松散地耦合一个新的应用程序。我的问题是如何将状态信息传递给用户。在过去,所有代码都嵌入到GUI中,如果非常混乱且无法维护,那将非常容易。类的安排是这样的(请不要检查我的UML技能 - 它们不存在):

Application Overview

如果我们采取右手边。 AirportsInformationRepository只存储数据,并在询问时将其提供给Controller。在开始时,它使用Persist类获取信息,以从用户硬盘驱动器获取与给定过滤器匹配的文件。它使用反编译器从文件中提取信息。所有这一切都很好,信息本身就可以到达GUI。

与此同时,我的问题是如何告诉用户发生了什么。这可以在反编译器中发生,例如,如果它获得的文件不能被反编译或者可能不包含数据。如果配置文件告诉谎言并且某个文件夹不存在,它可能出现在Persist类中。除非发生致命错误,否则这些问题不应该停止。

如果存在致命错误,则需要立即返回给用户,整个过程应该停止。否则,可以通过该过程以某种方式收集警告,然后在扫描完成时显示。

我熟悉日志记录,应用程序确实有一个记录器,用于监视应用程序是否存在未处理的异常和其他故障。这写入磁盘,我使用它像大多数会处理错误。我不想将此用于状态报告给用户,因为说实话,如果找不到文件或者用户输入了配置文件的无效路径,应用程序没有任何问题。

我考虑过:

  • 在每个类中累积日志,并在进程完成(或失败)时将其传递回使用者。我发现真的很乱
  • 使用事件,但如果消费者正在订阅,并且事件以与日志相同的方式传递链,那么我不认为这更好。我想另一种方法是让GUI直接订阅,但它不应该对反编译器有任何了解....
  • 一个家庭滚动记录器,它是静态类或在program.cs中实例化
  • 某种消息传递框架 - 我承认我不清楚,但我认为它与中央事件处理程序非常相似,GUI可以订阅它而无需了解其他类的任何内容。

总结一下。什么是积累“一切照旧”状态信息并在扫描结束时将其提供给GUI的最佳方式,同时能够在致命问题上停止。

提前感谢您阅读本文。道歉的长度道歉。

修改 我应该说应用程序正在使用NET 3.5。我会改变这个以获得一个优雅的解决方案,但....

2 个答案:

答案 0 :(得分:3)

当你处理一个典型的"流程"处理,甚至可能是多线程/并发处理流程,最好的方法是采用邮件槽和消息泵。通过交换消息,您可以轻松地协调应用程序的多个层,包括报告错误,通知下一个命令链等。我不是指Windows消息,我的意思是:

public abstract class Message
{
   public abstract void Process();
} 

然后:

public class MessageQueue
{
   private Queue m_Queue;
   public void Post(Message msg) {....}
   public void Process() {.....}
}

然后在应用的每个线程/处理层上分配MessageQueue并传递消息,如下所示:

    GUIMessageQueue.Post(
           new ErrorMessage("Internal async file reader ran out of buffer"));  

在GUI线程上放置一个读取GUI队列的计时器并调用它的Process()。 现在,您可以创建许多消息派生的工作项,以执行在线程/逻辑层之间轻松编排的各种任务。此外,消息可能包含与它们相关的数据的引用:

public AirplaneLandedMessage:消息    {       公共飞机......    }

以下是我在大规模并行链处理系统中使用的一些实际代码:

/// <summary>
/// Defines a base for items executable by WorkQueue
/// </summary>
public interface IWorkItem<TContext> where TContext : class
{
  /// <summary>
  /// Invoked on an item to perform actual work. 
  /// For example: repaint grid from changed data source, refresh file, send email etc... 
  /// </summary>
  void PerformWork(TContext context);

  /// <summary>
  /// Invoked after successfull work execution - when no exception happened
  /// </summary>
  void WorkSucceeded();

  /// <summary>
  /// Invoked when either work execution or work success method threw an exception and did not succeed
  /// </summary>
  /// <param name="workPerformed">When true indicates that PerformWork() worked without exception but exception happened later</param>
  void WorkFailed(bool workPerformed, Exception error);

}


/// <summary>
/// Defines contract for work queue that work items can be posted to
/// </summary>
public interface IWorkQueue<TContext> where TContext : class
{
  /// <summary>
  /// Posts work item into the queue in natural queue order (at the end of the queue)
  /// </summary>
  void PostItem(IWorkItem<TContext> work);

  long ProcessedSuccessCount{get;}
  long ProcessedFailureCount{get;}

  TContext Context { get; }
}

答案 1 :(得分:1)

嗯,致命的错误是容易的部分,它们只是通过例外处理。工作线程在遇到阻止继续工作的问题时可以抛出异常,然后在它冒泡到UI层的某个时刻,您可以捕获异常并向用户显示相应的错误消息而不实际崩溃的应用程序。您可以使用不同类型的异常和异常消息来允许不同的问题导致不同的UI响应。

至于指示非致命状态,您可以使用IProgress界面。在您的UI层中,您可以创建一个Progress实例,在调用时,更新......具有新状态的任何内容。然后,您可以将IProgress实例传递给工作类,并在有信息提供时将其激活。

由于你已经预先准备好4.5了,所以很容易重写这个课程。

public interface IProgress<T>
{
    public void Report(T parameter);
}

public class Progress<T>:IProgress<T>
{
    public event Action<T> ProgressChanged;

    public Progress() { }
    public Progress(Action<T> action)
    {
        ProgressChanged += action;
    }

    void IProgress<T>.Report(T parameter)
    {
        ProgressChanged(parameter);
    }
}

注意:真正的Progress类将事件编组到UI线程中,我没有添加该部分,因此要么添加它,要么在事件处理程序中执行。