ui 上的后台工作人员更新日志
Backgroundworker updating log on the ui
我有一个 wpf 应用程序,主 window 中有一个文本框,它应该用于在用户运行较长的进程时显示日志记录信息。
<TextBox Grid.Row="1" Margin="10,10,10,10" AcceptsReturn="True" Name="txtLogging" TextWrapping="WrapWithOverflow"
Text="{Binding Path=LogText, Mode=TwoWay}" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" />
public string LogText
{
get { return _logText; }
set
{
_logText = value;
OnPropertyChanged();
}
}
ui 上的其中一个按钮可启动至少需要 30 秒,有时长达数小时的过程。不用说,运行这个在后台工作人员是首选。问题是程序中的日志记录 class 是在 UI 线程上创建的,必须在 worker 执行期间访问以使用当前正在发生的日志更新 UI .
记录器看起来像这样;
using System;
using System.IO;
namespace BatchInvoice
{
public enum LoggingLevel
{
Verbose = 0,
Info = 1,
Warning = 2,
Error = 3
}
public sealed class Logger
{
string _logFile;
static Logger() { }
public bool LogToDataBase = false;
public bool LogToFile = true;
public bool LogToScreen = false;
private Logger()
{
//string filePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string filePath = Directory.GetCurrentDirectory();
filePath = filePath + @"\LogFiles";
string extension = ".log";
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
/*string currentDir = Environment.CurrentDirectory;
DirectoryInfo directory = new DirectoryInfo(currentDir);
string fullDirectory = directory.FullName;*/
string date = (DateTime.Now).ToString("yyyyMMddHHmmss");
_logFile = filePath + "\" + date + extension;
minimumLoggingLevel = LoggingLevel.Info;
}
private LoggingLevel minimumLoggingLevel;
public static void SetMinimumLoggingLevel(LoggingLevel minimum)
{
Instance.minimumLoggingLevel = minimum;
}
public static LoggingLevel GetMinimumLoggingLevel()
{
return Instance.minimumLoggingLevel;
}
private static readonly Logger instance = new Logger();
public static Logger Instance
{
get
{
return instance;
}
}
public static void Write(string content)
{
using (StreamWriter fileWriter = File.AppendText(Instance._logFile))
{
fileWriter.WriteLine(content);
}
}
public static void Write(string content, LoggingLevel warningLevel)
{
if (Instance.minimumLoggingLevel <= warningLevel)
{
if (Instance.LogToFile)
{
using (StreamWriter fileWriter = File.AppendText(Instance._logFile))
{
fileWriter.WriteLine(warningLevel.ToString() + ": " + content);
}
}
if (Instance.LogToScreen)
ScreenLogging.Write(content, warningLevel);
if (Instance.LogToDataBase)
{
//enter database loggign code here.
}
}
}
}
}
using System.Windows;
using System.Windows.Controls;
namespace BatchInvoice
{
public class ScreenLogging
{
private static ScreenLogging _instance;
private ScreenLogging() { }
public static ScreenLogging Instance
{
get
{
if(_instance == null)
{
_instance = new ScreenLogging();
}
return _instance;
}
}
private TextBox _target;
public static void SetTarget(TextBox target)
{
Instance._target = target;
}
public static void Write(string content, LoggingLevel warningLevel)
{
//MessageBox.Show(content, warningLevel.ToString());
Instance._target.AppendText(warningLevel.ToString() + ": " + content + "\n");
}
}
}
(是的,将屏幕记录分成不同的 class 是有原因的,但我真的希望我不必更改它)我该怎么做才能调用此记录 class 从后台工作者内部反思UI?我是否应该更改 LogText 属性 以从外部文件或类似的内容中读取?目前我没有实现后台工作者,所以日志记录只在任务完成后显示,但我需要能够在 运行 期间监控它的进度。当我尝试将它放入后台工作程序时,它在遇到一行试图访问记录器的代码时出错。
由于您正尝试从另一个线程更新 UI,因此您必须以特殊方式执行此操作,其中线程必须同步以在它们之间传输数据。换句话说,这就像 BackgroundWorker 需要暂停以更新 UI。可以使用 BackgroundWorker 的 ProgressChanged 事件和 ReportProgress 方法来完成。这是一个简单的例子:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// I guess this is how you are using your logger, right?
ScreenLogging.SetTarget(this.txtLogging);
BackgroundWorker worker = new BackgroundWorker();
// Your classic event to do the background work...
worker.DoWork += Worker_DoWork;
// Here you can sender messages to UI.
worker.ProgressChanged += Worker_ProgressChanged;
// Don't forget to turn this property to true.
worker.WorkerReportsProgress = true;
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
Thread.Sleep(3000);
// ReportProgress sends two values to the ProgressChanged method, for the
// ProgressChangedEventArgs object. The first one is the percentage of the
// work, and the second one can be any object that you need to pass to UI.
// In a simple example, I am passing my log message and just putting
// any random value at progress, since it does not matter here.
worker.ReportProgress(0, "Test!");
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Here you get your UserState object, wich is my string message passed on
// with the ReportProgress method above.
var message = e.UserState as string;
// Then you call your log as always. Simple, right?
ScreenLogging.Write(message, LoggingLevel.Info);
}
由于您的问题似乎不会重写您所有的日志调用,我将 post 另一种方法,只需更改 ScreenLogging.Write 方法。我希望这对你有用,因为你不需要更改对 Logger.Write 方法的调用。
public class ScreenLogging
{
private static ScreenLogging _instance;
private ScreenLogging() { }
public static ScreenLogging Instance
{
get
{
if (_instance == null)
{
_instance = new ScreenLogging();
}
return _instance;
}
}
private TextBox _target;
public static void SetTarget(TextBox target)
{
Instance._target = target;
}
public static void Write(string content, LoggingLevel warningLevel)
{
var appendTextAction = new Action(() =>
{
var text = warningLevel.ToString() + ": " + content + "\n";
Instance._target.AppendText(text);
});
// Only the thread that the Dispatcher was created on may access the
// DispatcherObject directly. To access a DispatcherObject from a
// thread other than the thread the DispatcherObject was created on,
// call Invoke and BeginInvoke on the Dispatcher the DispatcherObject
// is associated with.
// You can set the priority to Background, so you guarantee that your
// key operations will be processed first, and the screen updating
// operations will happen only after those operations are done.
Instance._target.Dispatcher.Invoke(appendTextAction,
DispatcherPriority.Background);
}
}
我有一个 wpf 应用程序,主 window 中有一个文本框,它应该用于在用户运行较长的进程时显示日志记录信息。
<TextBox Grid.Row="1" Margin="10,10,10,10" AcceptsReturn="True" Name="txtLogging" TextWrapping="WrapWithOverflow"
Text="{Binding Path=LogText, Mode=TwoWay}" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" />
public string LogText
{
get { return _logText; }
set
{
_logText = value;
OnPropertyChanged();
}
}
ui 上的其中一个按钮可启动至少需要 30 秒,有时长达数小时的过程。不用说,运行这个在后台工作人员是首选。问题是程序中的日志记录 class 是在 UI 线程上创建的,必须在 worker 执行期间访问以使用当前正在发生的日志更新 UI .
记录器看起来像这样;
using System;
using System.IO;
namespace BatchInvoice
{
public enum LoggingLevel
{
Verbose = 0,
Info = 1,
Warning = 2,
Error = 3
}
public sealed class Logger
{
string _logFile;
static Logger() { }
public bool LogToDataBase = false;
public bool LogToFile = true;
public bool LogToScreen = false;
private Logger()
{
//string filePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string filePath = Directory.GetCurrentDirectory();
filePath = filePath + @"\LogFiles";
string extension = ".log";
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
/*string currentDir = Environment.CurrentDirectory;
DirectoryInfo directory = new DirectoryInfo(currentDir);
string fullDirectory = directory.FullName;*/
string date = (DateTime.Now).ToString("yyyyMMddHHmmss");
_logFile = filePath + "\" + date + extension;
minimumLoggingLevel = LoggingLevel.Info;
}
private LoggingLevel minimumLoggingLevel;
public static void SetMinimumLoggingLevel(LoggingLevel minimum)
{
Instance.minimumLoggingLevel = minimum;
}
public static LoggingLevel GetMinimumLoggingLevel()
{
return Instance.minimumLoggingLevel;
}
private static readonly Logger instance = new Logger();
public static Logger Instance
{
get
{
return instance;
}
}
public static void Write(string content)
{
using (StreamWriter fileWriter = File.AppendText(Instance._logFile))
{
fileWriter.WriteLine(content);
}
}
public static void Write(string content, LoggingLevel warningLevel)
{
if (Instance.minimumLoggingLevel <= warningLevel)
{
if (Instance.LogToFile)
{
using (StreamWriter fileWriter = File.AppendText(Instance._logFile))
{
fileWriter.WriteLine(warningLevel.ToString() + ": " + content);
}
}
if (Instance.LogToScreen)
ScreenLogging.Write(content, warningLevel);
if (Instance.LogToDataBase)
{
//enter database loggign code here.
}
}
}
}
}
using System.Windows;
using System.Windows.Controls;
namespace BatchInvoice
{
public class ScreenLogging
{
private static ScreenLogging _instance;
private ScreenLogging() { }
public static ScreenLogging Instance
{
get
{
if(_instance == null)
{
_instance = new ScreenLogging();
}
return _instance;
}
}
private TextBox _target;
public static void SetTarget(TextBox target)
{
Instance._target = target;
}
public static void Write(string content, LoggingLevel warningLevel)
{
//MessageBox.Show(content, warningLevel.ToString());
Instance._target.AppendText(warningLevel.ToString() + ": " + content + "\n");
}
}
}
(是的,将屏幕记录分成不同的 class 是有原因的,但我真的希望我不必更改它)我该怎么做才能调用此记录 class 从后台工作者内部反思UI?我是否应该更改 LogText 属性 以从外部文件或类似的内容中读取?目前我没有实现后台工作者,所以日志记录只在任务完成后显示,但我需要能够在 运行 期间监控它的进度。当我尝试将它放入后台工作程序时,它在遇到一行试图访问记录器的代码时出错。
由于您正尝试从另一个线程更新 UI,因此您必须以特殊方式执行此操作,其中线程必须同步以在它们之间传输数据。换句话说,这就像 BackgroundWorker 需要暂停以更新 UI。可以使用 BackgroundWorker 的 ProgressChanged 事件和 ReportProgress 方法来完成。这是一个简单的例子:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// I guess this is how you are using your logger, right?
ScreenLogging.SetTarget(this.txtLogging);
BackgroundWorker worker = new BackgroundWorker();
// Your classic event to do the background work...
worker.DoWork += Worker_DoWork;
// Here you can sender messages to UI.
worker.ProgressChanged += Worker_ProgressChanged;
// Don't forget to turn this property to true.
worker.WorkerReportsProgress = true;
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
Thread.Sleep(3000);
// ReportProgress sends two values to the ProgressChanged method, for the
// ProgressChangedEventArgs object. The first one is the percentage of the
// work, and the second one can be any object that you need to pass to UI.
// In a simple example, I am passing my log message and just putting
// any random value at progress, since it does not matter here.
worker.ReportProgress(0, "Test!");
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Here you get your UserState object, wich is my string message passed on
// with the ReportProgress method above.
var message = e.UserState as string;
// Then you call your log as always. Simple, right?
ScreenLogging.Write(message, LoggingLevel.Info);
}
由于您的问题似乎不会重写您所有的日志调用,我将 post 另一种方法,只需更改 ScreenLogging.Write 方法。我希望这对你有用,因为你不需要更改对 Logger.Write 方法的调用。
public class ScreenLogging
{
private static ScreenLogging _instance;
private ScreenLogging() { }
public static ScreenLogging Instance
{
get
{
if (_instance == null)
{
_instance = new ScreenLogging();
}
return _instance;
}
}
private TextBox _target;
public static void SetTarget(TextBox target)
{
Instance._target = target;
}
public static void Write(string content, LoggingLevel warningLevel)
{
var appendTextAction = new Action(() =>
{
var text = warningLevel.ToString() + ": " + content + "\n";
Instance._target.AppendText(text);
});
// Only the thread that the Dispatcher was created on may access the
// DispatcherObject directly. To access a DispatcherObject from a
// thread other than the thread the DispatcherObject was created on,
// call Invoke and BeginInvoke on the Dispatcher the DispatcherObject
// is associated with.
// You can set the priority to Background, so you guarantee that your
// key operations will be processed first, and the screen updating
// operations will happen only after those operations are done.
Instance._target.Dispatcher.Invoke(appendTextAction,
DispatcherPriority.Background);
}
}