Вопрос по delegates, .net, multithreading, c# – C # Cross-Thread Communication

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& APOS; Я вижу, что это фоновая тема! может быть, это нормально, но как я могу предотвратить такое поведение? если вы проверите & apos;System.Windows.Forms.Timer& APOS; компонент и обрабатывать его & apos;Tick& APOS; событие, вы можете увидеть, что & apos;Thread.CurrentThread.ManagedThreadId& APOS; не был изменен с основного идентификатора потока на что-либо еще.

Что я могу сделать?

System.Windows.Forms.Timer использует внутреннее событие Windows WM_TIMER и просто считает количество тактов, а затем вызывает событие .NET. Поэтому он может работать в основном потоке процесса. Фоновый рабочий, который вы написали, работает в фоновом потоке (явно создавая новый Thread ()), в противном случае Thread.Sleep (3000) приостановит все ваше приложение. В чем проблема с этим работает в фоновом потоке точно? Kuba Wyrostek
@MareInfinitus, когда фоновый рабочий объект вызывает свое событие или вызывает указанный метод, там я вижу измененный идентификатор потока. это означает, что мой фоновый работник вызвал эти методы. losingsleeep
Это верно, вызываемые методы находятся не в фоновом рабочем потоке, а в потоке, который предоставляет эти обратные вызовы. Mare Infinitus
@KubaWyrostek, спасибо. Я хочу решить (изменить) поведение фонового рабочего, чтобы в дальнейшем я был в безопасности, взаимодействуя с элементами пользовательского интерфейса. кажется, что я должен написать Backgrounder, используя нативные Win API, точно так же, как .NET Timer, не так ли? но как это работает в независимом классе без использования потоков !? losingsleeep
В вашем коде нет проблем, и вы просто хотите понять, верно? Фоновый работникis в другой теме. Где вы смотрите ID потока? Mare Infinitus

Ваш Ответ

5   ответов
0

Я думаю, что есть другое решение, которое не требует ссылки на «System.Windows.Forms». в нижележащем слое (layer2):
layer1 (форма пользовательского интерфейса) предоставляет реализацию тайм-аута, которую может использовать 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
        }

    }
}  

и в интерфейсе у нас есть это:

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) для выполнения некоторых работ. ядро нуждается в неблокирующих проверках тайм-аута во время некоторых своих методов без создания другого потока, потому что после тайм-аута ему нужно снова взаимодействовать с пользовательским интерфейсом. извините, если я не могу описать больше, потому что это немного сложно (для меня!).

Таймер блокирует основной поток. Не существует ничего похожего на неблокирующее выполнение кода без разных потоков в одной области приложения.
Да, Гжегож. В этом примере, кроме компонента Timer, все блокируется, и для меня это не проблема. я просто хотел использовать таймаут без создания другого потока. losingsleeep
0

Что именно вас беспокоит в фоновом потоке? То, что вы описываете, выглядит хорошо для меня до сих пор.

System.Windows.Forms.Timer что вы проверили для сравнения может быть запущенon the UI threadчто действительно сделало бы это второстепенным потоком.

Из документации MSDNThread.IsBackground собственность (выделено мной):

A thread is either a background thread or a foreground thread. Background threads are identical to foreground threads, except that background threads do not prevent a process from terminating. Once all foreground threads belonging to a process have terminated, the common language runtime ends the process. Any remaining background threads are stopped and do not complete.

Any remaining threads are stopped and do not complete  - Я думаю, что все они выбрасывают ThreadAbortException
1

Таймер запускается в главном потоке (и он запаздывает, если основной поток действительно занят). Если по какой-то причине вы хотите, чтобы jobDone () вызывался из основного потока, вы можете, например, использовать объект BackGroundWorker, который имеет внутреннюю многопоточность и имеет некоторые конкретные события, которые называются безопасными для потока с основным потоком (допускает обновления пользовательского интерфейса и т. Д.)

0

Как насчет этого дизайна:

  1. Create an EventWaitHandle object in your Form.

    private EventWaitHandle layer2changed =
        new EventWaitHandle(false, EventResetMode.ManualReset);
    
  2. In a Timer event handler do the following:

    if (this.layer2changed.WaitOne(0, false))
    {
        // perform UI updates according to some public properties of layer1/layer2
        this.layer2changed.Reset();
    }
    
  3. Pass the reference to layer2changed to layer1/layer2 etc. in their constructors.

  4. Every time your Done() method does something that requires updating UI signal an event:

     this.referencedLayer2Changed.Set();
    

Обратите внимание, однако, что между изменениями в layer2 и UI будет разница во времени (чем больше интервал Form.Timer - тем больше разница).

Note to everybody: this a soley a solution emerging from additional requirements specified by losingsleep in comments to his question. Please read them prior to downvoting. :-)

пожалуйста, посмотрите на мой собственный ответ. еще раз спасибо. losingsleeep
4

Если вы используете Windows Forms, вы можете сделать что-то вроде этого:

  1. In your Form add property

    private readonly System.Threading.SynchronizationContext context;
    public System.Threading.SynchronizationContext Context
    {
        get{ return this.context;}
    }
    
  2. In Your Form constructor set the property

    this.context= WindowsFormsSynchronizationContext.Current;
    
  3. Use this property to pass it to Your background worker as a constructor parameter. This way your worker will know about your GUI context. Create similar property inside Your background worker.

    private readonly System.Threading.SynchronizationContext context;
    public System.Threading.SynchronizationContext Context
    {
        get{ return this.context;}
    }
    
    public MyWorker(SynchronizationContext context)
    {
        this.context = context;
    }
    
  4. Change your Done() method:

    void Done()
    {
        this.Context.Post(new SendOrPostCallback(DoneSynchronized), null);
    }
    
    void DoneSynchronized(object state)
    {
        //place here code You now have in Done method.
    }
    
  5. In DoneSynchronized You should always be in Your GUI thread.

@GrzegorzWilczura, спасибо, я попробую ... losingsleeep
Хорошее решение. Обратите внимание, что свойство Context для BackgroundWorker должно быть доступно только для чтения или синхронизировано с его установщиком.
@ GrzegorzWilczura, спасибо за вашу помощь, но если я решу обратиться к & quot; System.Windows.Forms & quot; В моем нижележащем слое, вы не думаете, что лучше использовать «Таймер»? компонент вместо & quot; WindowsFormsSynchronizationContext & quot ;? Я проверил его, и он отлично работает, потому что наиболее часто используемое приложение - это форма Windows. я думаю, что это проще в использовании. losingsleeep
@KubaWyrostek Правда это! Я изменил свойства.
@GrzegorzWilczura, пожалуйста, посмотрите на мой собственный ответ. Большое спасибо за вашу помощь, мой друг. losingsleeep

Похожие вопросы