你能让一个对象的方法可以被所有线程中的所有代码访问吗?

时间:2013-01-21 17:47:52

标签: c# wpf multithreading user-interface

感谢您对此问题的所有兴趣。你们中的一些人要求更清楚地了解所涉及的代码,因此为了提供更多信息,我将对其进行编辑以提供更多细节。

my previous question有关,我试图在WPF窗口中模拟一个基本控制台(仅限文本输出)。这意味着可以使用在后台运行的大量代码在不同的线程上运行的程序。这段代码也很大程度上依赖于while循环,因此我的计划是将WPF控制台窗口保留在主线程上(以及可能需要的任何其他GUI窗口)并在不同的线程上执行所有代码。

窗口有一个像这样使用的WriteLine方法:

mainConsole.WriteLine("This is a message for the user.", SomeSender);

其余代码需要定期调用此方法。

其他信息:

窗口本身由包裹在Scroller中的Textblock组成。窗口的WriteLine方法将消息和格式(字体,字体大小和颜色 - 取决于消息的发件人是谁)添加到包含此信息的对象列表,然后显示这些消息的列表,包括它们格式化)作为Textblock的内容。该方法完全按预期工作,因此无需重写,只需要可访问。

我试图尽可能简洁地保持这种描述。有关详细信息,请参阅my previous question

所以现在我的问题是:是否有一种有效的方法使窗口的WriteLine方法可用于任何类的所有线程,从而使我能够像Console.WriteLine()一样使用它? < / p>

4 个答案:

答案 0 :(得分:1)

虽然你有很多选择,但听起来,在你的情况下,任何人都可以写入你的控制台。鉴于此,我会创建这样的东西:

public class MyConsole
{
    public static event Action<string> TextWritten;
    public static void Write(object obj)
    {
        string text = (obj ?? "").ToString();
        if (TextWritten != null)
            TextWritten(text);
    }

    public static void WriteLine(object obj)
    {
        Write(obj + "\n");
    }
}

然后让您的控制台表单订阅TextWritten事件,并在写入文本时将该文本写入控制台。 (确保首先封送到UI线程。)

这里使用和事件的主要优点,就是让这个类直接处理你的表单,就是你可以简单地添加额外的事件处理程序,允许你与标准输入/输出交互,添加额外的文件记录,一次打开多个控制台表单等。这种灵活性对于调试(即对平面文件的附加写入)和生产(通过标准输入/输出更容易重定向)都很有用。

答案 1 :(得分:1)

您似乎正在尝试编写一个日志记录服务,该服务允许您从代码中的任何位置访问日志。你提到线程,所以你必须注意并相应地处理这种同步。

我首先要创建一个ILogger接口,例如:

public interface ILogger
{
  void Log(string line);
  void Log(string format, params object[] args);
}

然后是一个合适的Logger基类:

public abstract class Logger : ILogger
{
  public abstract void Log(string line);

  public virtual void Log(string format, params object[] args)
  {
    Log(string.Format(format, args));
  }
}

当然,您需要一个实际的实现:

using System.Collections.Concurrent;
using System.Threading.Tasks;

public class ConcurrentLogger : Logger, ILogger, IDisposable
{
  bool isDisposed;
  BlockingCollection<string> loggedLines;
  Action<string> callback;

  public ConcurrentLogger(Action<string> callback)
  {
    if (callback == null)
      throw new ArgumentNullException("callback");

    var queue = new ConcurrentQueue<string>();
    this.loggedLines = new BlockingCollection<string>(queue);

    this.callback = callback;

    StartMonitoring();
  }

  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool isDisposing)
  {
    if (isDisposed) return;

    if (isDisposing)
    {
      if (loggedLines != null)
        loggedLines.CompleteAdding();
    }

    isDisposed = true;
  }

  public override void Log(string line)
  {
    if (!loggedLines.IsAddingCompleted)
      loggedLines.Add(line);
  }

  protected virtual void StartMonitoring()
  {
    Task.Factory.StartNew(() =>
      {
        foreach (var line in loggedLines.GetConsumingEnumerable())
        {
          if (callback != null)
            callback(line);
        }

        loggedLines.Dispose();

      }, TaskCreationOptions.LongRunning);
  }
}

对于全局访问权限,您需要一个Singleton类,因此我将创建此LogManager类:

public sealed class LogManager : ILogger
{
  #region Singleton
  static readonly LogManager instance = new LogManager();

  public static LogManager Current { get { return instance; } }

  private LogManager() { } // Disallow creating instances.
  #endregion

  ILogger logger;

  public ILogger Logger { get { return logger; } }

  public void StartLogging(ILogger logger)
  {
    if (logger == null)
      throw new ArgumentNullException("logger");

    this.logger = logger;
  }

  public void StopLogging(bool dispose = true)
  {
    var previousLogger = this.logger as IDisposable;
    this.logger =null;

    if (previousLogger != null && dispose)
      previousLogger.Dispose();
  }

  public void Log(string line)
  {
    if (logger != null) logger.Log(line);
  }

  public void Log(string format, params object[] args)
  {
    if (logger != null) logger.Log(format, args);
  }
}

快速初始化:

void InitializeLog()
{
  var log = new ConcurrentLogger(LogToTextBox);
  LogManager.Current.StartLogging(log);
}

void LogToTextBox(string line)
{
  if (!CheckAccess())
  {
    this.Dispatcher.BeginInvoke((Action<string>)LogToTextBox,
                                DispatcherPriority.Background, 
                                line);
    return;
  }

  logTextBox.AppendText(line + Environment.NewLine);
}

然后,您可以在代码中的任意位置拨打:LogManager.Current.Log(...);

答案 2 :(得分:0)

创建一个包含WriteLine方法的静态类和一个引用窗口,控件或者在writeline方法中需要的任何内容的属性。 然后向MainWindow构造函数或已加载事件添加一些代码,以将Reference-Property设置为所需项。 之后,您可以随时随地使用Writeline。

BTW:使用带有Instance getter的静态MainViewModel可以更清晰,将MainWindow的DataContext绑定到此ViewModel并使用MVVM模式。然后,您只需设置一些ConsoleOutput属性或从任何地方调用AddLine方法甚至Command,并且不必知道View如何显示它。您可以使用单元测试来测试您的应用程序,您可以更改可视化表示,...所有这些都可以触及您的应用程序的逻辑。

答案 3 :(得分:-1)

namespace {yourrootnamespace}
{
   namespace GlobalMethods
   {
        static class ConsoleMethods
        {
        mainConsole mc;
        public static WriteLine(string msg, object sender)
        {
            lock (this)
            {
                mc.WriteLine(msg, sender)
            }
        }
        static ConsoleMethods()
        {
            mc = new mainConsole();
        }
        //more methods
    }
}

然后:using {yourrootnamespace}.GlobalMethods;

或者让方法接受一个mainConsole参数,然后用它来调用。