4

在 C#.NET 中,我编写了以下简单的后台工作线程:

public class MyBackgrounder
{
    public delegate void dlgAlert();
    public dlgAlert Alert;
    public event EventHandler eventAlert;
    Thread trd;

    public void Start()
    {
        if (trd == null || trd.ThreadState == ThreadState.Aborted)
        {
            trd = new Thread(new ThreadStart(Do));
        }
        trd.IsBackground = true;
        trd.Priority = ThreadPriority.BelowNormal;
        trd.Start();
    }

    void Do()
    {

        Thread.Sleep(3000);
        Done();
    }

    void Done()
    {
        if (Alert != null)
            Alert();
        if (eventAlert != null)
            eventAlert(this, new EventArgs());
        Kill();
    }

    public void Kill()
    {
        if (trd != null)
            trd.Abort();
        trd = null;
    }
}


static class Program
{

    [STAThread]
    static void Main()
    {
        MyBackgrounder bg = new MyBackgrounder();
        bg.eventAlert += new EventHandler(bg_eventAlert);
        bg.Alert = jobDone;
        bg.Start();
    }

    static void bg_eventAlert(object sender, EventArgs e)
    {
        // here, current thread's id has been changed
    }

    static void jobDone()
    { 
        // here, current thread's id has been changed
    }

}

它只等待 3 秒(完成它的工作),然后引发指定事件或调用委托。直到这里没有问题,一切正常。但是当我观看“ Thread.CurrentThread.ManagedThreadId ”时,我看到它是后台线程!也许这很正常,但我怎样才能防止这种行为?如果您测试“ System.Windows.Forms.Timer ”组件并处理其“ Tick ”事件,您可以看到“ Thread.CurrentThread.ManagedThreadId ”没有从主线程 ID 更改为其他任何内容。

我能做些什么?

4

5 回答 5

5

如果您使用的是 Windows 窗体,则可以执行以下操作:

  1. 在您的表单中添加属性

    private readonly System.Threading.SynchronizationContext context;
    public System.Threading.SynchronizationContext Context
    {
        get{ return this.context;}
    }
    
  2. 在您的表单构造函数中设置属性

    this.context= WindowsFormsSynchronizationContext.Current;
    
  3. 使用此属性将其作为构造函数参数传递给您的后台工作人员。这样,您的工作人员将了解您的 GUI 上下文。在您的后台工作人员中创建类似的属性。

    private readonly System.Threading.SynchronizationContext context;
    public System.Threading.SynchronizationContext Context
    {
        get{ return this.context;}
    }
    
    public MyWorker(SynchronizationContext context)
    {
        this.context = context;
    }
    
  4. 改变你的Done()方法:

    void Done()
    {
        this.Context.Post(new SendOrPostCallback(DoneSynchronized), null);
    }
    
    void DoneSynchronized(object state)
    {
        //place here code You now have in Done method.
    }
    
  5. 在 DoneSynchronized 您应该始终在您的 GUI 线程中。

于 2012-06-30T13:10:19.403 回答
1

Timer 在主线程中运行(如果主线程真的很忙,将会迟到)。如果由于某种原因您希望从主线程调用 jobDone(),例如,您可以使用 BackGroundWorker 对象,该对象在内部线程并具有一些特定事件,这些事件被称为线程安全的主线程(允许 UI 更新等)

于 2012-06-30T13:11:21.270 回答
0

我认为还有另一种解决方案不需要在底层(layer2)中引用“System.Windows.Forms”:
layer1(UI 表单)提供了 layer2 可以使用的超时实现。

有一个接口:

namespace Layer2
{
    public delegate void TimeoutAlert();

    public interface IApplicationContext
    {
        void SetTimeout(TimeoutAlert timeoutAlerthandler, int milliseconds);
    }
}

和 Layer2 中的工人阶级:

namespace Layer2
{
    public class Worker
    {
        IApplicationContext _context;
        public void DoJob(IApplicationContext context)
        {
            _context = context;
            _context.SetTimeout(JobTimedOut,3000);
            // nothing ... (wait for other devices or user events)
        }

        void JobTimedOut()
        {
            // do something suitable for timeout error with _context or anything else
        }

    }
}  

在 UI 中,我们有这个:

using Layer2;

namespace Layer1
{
    public partial class frmTest : Form, IApplicationContext
    {

        int timeout;
        TimeoutAlert _timeoutAlerthandler;

        public frmTest()
        {
            InitializeComponent();
        }

        private void frmTest_Load(object sender, EventArgs e)
        {
            Worker w = new Worker();
            w.DoJob(this);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            // it's the handler of 'Timer' component named 'timer1'
            timer1.Enabled = false;

            if (_timeoutAlerthandler != null)
                _timeoutAlerthandler();
        }

        #region IApplicationContext Members

        void IApplicationContext.SetTimeout(TimeoutAlert timeoutAlerthandler, int milliseconds)
        {
            _timeoutAlerthandler = timeoutAlerthandler;
            timeout = milliseconds;

            timer1.Interval = milliseconds;
            timer1.Enabled = true;
        }

        #endregion

    }
}  

我再次注意到我的场景:
我有一个 Windows 应用程序项目 (layer1),它使用一个核心项目 (layer2) 来完成一些工作。核心需要在其某些方法期间进行非阻塞超时检查,而无需创建不同的线程,因为超时后它需要再次与 UI 交互。对不起,如果我不能描述更多,因为它有点复杂(对我来说!)。

于 2012-07-01T07:36:16.710 回答
0

这个设计怎么样:

  1. 在您的表单中创建一个 EventWaitHandle 对象。

    private EventWaitHandle layer2changed =
        new EventWaitHandle(false, EventResetMode.ManualReset);
    
  2. 在 Timer 事件处理程序中执行以下操作:

    if (this.layer2changed.WaitOne(0, false))
    {
        // perform UI updates according to some public properties of layer1/layer2
        this.layer2changed.Reset();
    }
    
  3. 在它们的构造函数中传递对 layer2changed 的​​引用到 layer1/layer2 等。

  4. 每次您的 Done() 方法执行需要更新 UI 的操作时都会发出一个事件信号:

     this.referencedLayer2Changed.Set();
    

但是请注意,layer2 和 UI 的变化会有时间差异(Form.Timer 间隔越长 - 差异越长)。

请注意:这是一个单独的解决方案,它来自于 losssleep 在对他的问题的评论中指定的附加要求。请在投票之前阅读它们。:-)

于 2012-06-30T13:55:36.740 回答
0

到底是什么让你对后台线程感到厌烦?你所描述的,到目前为止对我来说还不错。

您检查比较的System.Windows.Forms.Timer那个可能正在UI 线程上运行,这确实会使其成为非后台线程。

来自Thread.IsBackground属性的 MSDN 文档(我强调):

线程要么是后台线程,要么是前台线程。后台线程前台线程相同,只是后台线程不会阻止进程终止。一旦属于一个进程的所有前台线程都已终止,公共语言运行时就会结束该进程。任何剩余的后台线程都将停止并且不会完成。

于 2012-06-30T11:13:56.557 回答