Проблема с многопоточностью в Visual Studio Debugger - PullRequest
2 голосов
/ 07 августа 2009

У меня есть приложение, предназначенное для работы в сети. Это означает, что первоначальный запуск этого приложения может занять некоторое время. Поэтому я собрал заставку, чтобы немного улучшить процесс.

Он использует многопоточность для отображения формы статическим методом. Я все еще новичок в потоке, поэтому, когда я получал ошибки, я был немного озадачен тем, что и почему.

Оказывается, мой код прекрасно работает, когда я запускаю его вне отладчика Visual Studio. Но когда я запускаю его из отладчика, я получаю исключение:

"Операция с несколькими потоками недопустима: доступ к элементу управления 'выполняется из потока, отличного от потока, в котором он был создан."

Вот мой класс:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace MyApp
{
    public partial class SplashScreen : Form
    {
        private static double OPACITY_INCREMENT = .05;

        private static SplashScreen _splashForm;
        private static SplashScreen SplashForm
        {
            get { return SplashScreen._splashForm; }
            set { SplashScreen._splashForm = value; }
        }

        private static void ShowForm()
        {
            SplashScreen.SplashForm = new SplashScreen();            
            Application.Run(SplashScreen.SplashForm);
        }

        internal static void CloseForm()
        {
            if (SplashScreen.SplashForm != null &&
                SplashScreen.SplashForm.IsDisposed == false)
            {
                // Make it start going away.
                OPACITY_INCREMENT = -OPACITY_INCREMENT;
            }

            SplashScreen.SplashThread = null;
            SplashScreen.SplashForm = null;
        }

        private static Thread _splashThread;
        private static Thread SplashThread
        {
            get { return SplashScreen._splashThread; }
            set { SplashScreen._splashThread = value; }
        }

        internal static void ShowSplashScreen()
        {
            if (SplashScreen.SplashForm != null)
            {
                return;
            }

            SplashScreen.SplashThread = new Thread(new ThreadStart(SplashScreen.ShowForm));
            SplashScreen.SplashThread.IsBackground = true;
            SplashScreen.SplashThread.SetApartmentState(ApartmentState.STA);
            SplashScreen.SplashThread.Start();
        }

        public SplashScreen()
        {
            InitializeComponent();
            this.timerFade.Start();
            this.ClientSize = this.BackgroundImage.Size;
        }

        private void SplashScreen_Load(object sender, EventArgs e)
        {
        }

        private void timerFade_Tick(object sender, EventArgs e)
        {
            if (OPACITY_INCREMENT > 0)
            {
                if (this.Opacity < 1)
                    this.Opacity += OPACITY_INCREMENT;
            }
            else
            {
                if (this.Opacity > 0)
                    this.Opacity += OPACITY_INCREMENT;
                else
                {
                    this.Invoke(new MethodInvoker(this.TryClose));
                }
            }
        }

        private void TryClose()
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new MethodInvoker(this.TryClose));
            }

            this.Close();
        }
    }
}

Я вызываю заставку из метода Program.cs Main.

namespace CIMA
{
    static class Program
    {
// <summary>
        ///     The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {            
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            SplashScreen.ShowSplashScreen();

            // Code omitted for brevity.

            SplashScreen.CloseForm();

            // Code omitted for brevity.
        }
    }
}

Я хотел бы иметь возможность вызывать SplashScreen.CloseForm () из одной из моих других форм, но я еще не настолько пробовал это сделать. Я не понимаю, что задумал отладчик.

Я что-то упустил? Или мне нужно отключить заставку, если она запускается в отладчике?

Если это так, какой хороший способ сделать это? Я хочу избегать использования символов компиляции, если это возможно, потому что я ненавижу их отслеживать.

Ответы [ 3 ]

4 голосов
/ 07 августа 2009

В .NET вы не можете получить доступ к элементу управления из потока, который не создал элемент управления. В вашем коде похоже, что отдельный поток создает форму-заставку, а основной поток пытается закрыть форму-заставку.

Я предлагаю вам сделать наоборот: создайте форму-заставку в главном потоке, а отдельный поток просто обновите ее.

Кроме того, в вашем методе TryClose отсутствует возврат после вызова BeginInvoke Я полагаю.

Однако вы можете посмотреть другие реализации для заставок. Есть некоторые готовые к использованию, например http://www.codeproject.com/KB/cs/prettygoodsplashscreen.aspx

1 голос
/ 10 августа 2009

Хорошо - вот другое решение. Я думаю, что это прямо сейчас. Я включаю его только в том случае, если кто-то другой столкнется с той же проблемой. Короткая версия: Торстен был прав.

Проблема заключалась в природе функции закрытия заставки. Я вызывал постепенное исчезновение в форме всплеска, используя таймер, встроенный в форму. Таймер уменьшит непрозрачность формы до нуля, а затем закроет всплеск при достижении нуля.

Чтобы решить проблему, я сделал несколько вещей:

  1. Переименовал мой оригинальный timerFade в timerFadeIn
  2. Создан новый таймер в форме Splash: timerFadeOut
  3. Изменены мои статические методы SpalshScreen, чтобы сама Форма создавалась в том же потоке, что и метод Program.Main.
  4. Организовал статические методы так, чтобы timerFadeOut.Start () вызывался из метода Program.Main.
  5. Использовал Invoke в обработчике события timerFadeOut_Tick как для понижающих шагов Opacity, так и для финального метода Close.

После этих изменений заставка работает как задумано. Ну, почти. Новый экран-заставка не начинает исчезать до тех пор, пока не откроется основная форма - основной поток должен получить достаточно обработки, чтобы таймер мог фактически начать выполнять свою работу. Но на самом деле это хорошо - я хотел изменить положение вещей, чтобы заставка заставки исчезала сразу после загрузки основной формы в любом случае , так что это оказалось плюсом.

Код ниже для заинтересованных. Кроме того, все еще есть вероятность того, что я неправильно реализовал это многопоточное поведение - я приглашаю критику. Как предложил Кайл, я хочу начать с хороших многопоточных привычек, а не плохих.

Для тех, кто не хочет эффекта FadeOut, я оставил в коде (закомментированный) закрытие формы без использования таймера в методе CloseForm ().

public partial class SplashScreen : Form
{
    private static double OPACITY_INCREMENT = .05;

    private static SplashScreen _splashForm;
    private static SplashScreen SplashForm
    {
        get { return SplashScreen._splashForm; }
        set { SplashScreen._splashForm = value; }
    }

    private static void ShowForm()
    {        
        Application.Run(SplashScreen.SplashForm);
    }

    internal static void CloseForm()
    {
        if (SplashScreen.SplashForm != null &&
            SplashScreen.SplashForm.IsDisposed == false)
        {
            // Make it start going away.
            SplashScreen.SplashForm.FadeOut();
        }

        //if (SplashScreen.SplashForm.InvokeRequired)
        //{
        //    SplashScreen.SplashForm.Invoke(new MethodInvoker(SplashScreen.SplashForm.Close));
        //}
        //else
        //{
        //    SplashScreen.SplashForm.DoClose();
        //}

        SplashScreen.SplashThread = null;
        SplashScreen.SplashForm = null;
    }

    private void FadeOut()
    {
        this.timerFadeOut.Start();
    }

    private static Thread _splashThread;
    private static Thread SplashThread
    {
        get { return SplashScreen._splashThread; }
        set { SplashScreen._splashThread = value; }
    }

    internal static void ShowSplashScreen()
    {
        if (SplashScreen.SplashForm != null)
        {
            return;
        }

        SplashScreen.SplashForm = new SplashScreen();
        SplashScreen.SplashThread = new Thread(new ThreadStart(SplashScreen.ShowForm));
        SplashScreen.SplashThread.IsBackground = true;
        SplashScreen.SplashThread.SetApartmentState(ApartmentState.STA);
        SplashScreen.SplashThread.Start();
    }

    public SplashScreen()
    {
        InitializeComponent();
        this.ClientSize = this.pictureBox1.Size;
    }

    private void SplashScreen_Load(object sender, EventArgs e)
    {

    }

    private void timerFadeIn_Tick(object sender, EventArgs e)
    {
        if (OPACITY_INCREMENT > 0)
        {
            if (this.Opacity < 1)
            {
                this.Opacity += OPACITY_INCREMENT;
            }
            else
            {
                this.timerFadeIn.Stop();
            }
        }
    }

    private void SplashScreen_Shown(object sender, EventArgs e)
    {
        this.timerFadeIn.Start();
    }

    private void timerFadeOut_Tick(object sender, EventArgs e)
    {
        if (OPACITY_INCREMENT > 0)
        {
            if (this.Opacity > 0)
            {
                if (this.InvokeRequired)
                {
                    this.Invoke(new MethodInvoker(this.FadeOutStep));
                }
                else
                {
                    this.FadeOutStep();
                }                    
            }
            else
            {
                this.timerFadeOut.Stop();

                if (this.InvokeRequired)
                {
                    this.Invoke(new MethodInvoker(this.Close));
                }
                else
                {
                    this.Close();
                }
            }
        }
    }

    private void FadeOutStep()
    {
        this.Opacity -= OPACITY_INCREMENT;
    }
}
0 голосов
/ 10 августа 2009

Хорошо - я нашел обходной путь.

System.Diagnostics.Debugger.IsAttached

Я добавил следующее выражение using:

using System.Diagnostics;

... и изменил метод ShowSplashScreen () следующим образом:

    internal static void ShowSplashScreen()
    {
        if (!Debugger.IsAttached)
        {
            if (SplashScreen.SplashForm != null)
            {
                return;
            }

            SplashScreen.SplashThread = new Thread(new ThreadStart(SplashScreen.ShowForm));
            SplashScreen.SplashThread.IsBackground = true;
            SplashScreen.SplashThread.SetApartmentState(ApartmentState.STA);
            SplashScreen.SplashThread.Start();
        }
    }

Таким образом, моя программа пропускает заставку, если я запускаю из Visual Studio, или она запускается с заставкой, если нет. Больше не нужно изменять мой код каждый раз, когда я тестирую пересборку.

Я все еще в растерянности из-за того, что у меня возникает ошибка при запуске внутри отладчика, а не для общего кода, поэтому я надеюсь, что какой-нибудь умный багер на этом сайте может прийти и объяснить это. *

В противном случае, этот обходной путь достаточно хорош для меня.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...