2

Есть приложение с нотификацией, которое отслеживает изменения в бд в реальном времени. Мне нужно чтобы но кнопке стоп поток приостанавливался, а по кнопке старт снова запускался. Я зашел в тупик, помогите пожалуйста. Сейчас по нажатию кнопки старт запускается еще +1 поток, а надо возобновлять остановленный(или что-то в этом роде). Как правильно остановить поток в коде, метод Abort работет через раз.

public partial class Form1 : Form
    {
    private SqlConnection connection = null;
    private SqlCommand command = null;
    private const string ServiceName = "ChangeNotifications";
    private int NotificationTimeout = 600;
    public Thread listener;
    private int p = 0;
    public Form1()
    {
        InitializeComponent();
    }
    private string GetConnectionString()
    {
        return "Data Source=localhost;Integrated Security=SSPI;" +
        "Initial Catalog=SQLNotificationRequestDB;Pooling=False;";
    }
    private string GetSQL()
    {
        return "SELECT id, flag, data from SQLNotificationRequestTable";
    }
    private string GetListenerSQL()
    {
        return "WAITFOR ( RECEIVE * FROM ChangeMessages);";
    }
    private void StartListener()
    {
            listener = new Thread(Listen);
            listener.Name = "Query Notification Watcher";
            listener.Start();

    }
    private void Listen()
    {
        using (SqlConnection connection =
        new SqlConnection(GetConnectionString()))
        {
            using (SqlCommand command =
            new SqlCommand(GetListenerSQL(), connection))
            {
                connection.Open();
                command.CommandTimeout = NotificationTimeout + 15;
                SqlDataReader reader = command.ExecuteReader();
                while (reader.Read())
                {
                    messageText = System.Text.ASCIIEncoding.ASCII.GetString((byte[])reader.GetValue(13)).ToString();
                }
            object[] args = { this, EventArgs.Empty };
                EventHandler notify =
                new EventHandler(OnNotificationComplete);
                this.BeginInvoke(notify, args);
                listener.Abort();
            }
        }
    }
    private string messageText;
    private void OnNotificationComplete(object sender, EventArgs e)
    {
        messageText = messageText.Replace("??", "");
        label1.Text = messageText.Replace("\0", "");
        LoadData();
        GetData();
    }

    private void GetData()
    {
        command.Notification = null;
        SqlNotificationRequest snr =
        new SqlNotificationRequest();
        snr.UserData = new Guid().ToString();
        snr.Options = "Service=" + ServiceName;
        snr.Timeout = NotificationTimeout;
        command.Notification = snr;
        StartListener();
    }
    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        listener.Abort();
        if (connection != null)
        {
            connection.Close();
        }
    }

    private void button1_Click_1(object sender, EventArgs e)
    {
        LoadData();
        if (connection == null)
        {
            connection = new SqlConnection(GetConnectionString());
        }
        if (command == null)
        {
            command = new SqlCommand(GetSQL(), connection);
        }
        if (button2.Enabled == true)
        {
            button1.Enabled = false;
        }
        GetData();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        listener.Abort();
        connection.Close();
        p++;
             if (button2.Enabled == true)
        {
            button1.Enabled = true;
        }

    }

    private void LoadData()
    {
        dataGridView1.Rows.Clear();
        string connectString = "Data Source=localhost;Integrated Security=SSPI;" +
        "Initial Catalog=SQLNotificationRequestDB;Pooling=False;";

        SqlConnection myConnection = new SqlConnection(connectString);

        myConnection.Open();

        string query = "SELECT id, flag, data from SQLNotificationRequestTable";

        SqlCommand command = new SqlCommand(query, myConnection);

        SqlDataReader reader = command.ExecuteReader();

        List<string[]> data = new List<string[]>();

        while (reader.Read())
        {
            data.Add(new string[3]);

            data[data.Count - 1][0] = reader[0].ToString();
            data[data.Count - 1][1] = reader[1].ToString();
            data[data.Count - 1][2] = reader[2].ToString();
        }

        reader.Close();

        myConnection.Close();

        foreach (string[] s in data)
            dataGridView1.Rows.Add(s);
    }
}

введите сюда описание изображения

введите сюда описание изображения

aepot
  • 49,560
ebw1910
  • 175
  • 9

1 Answers1

4

Вместо listener.Abort(); достаточно просто написать return;, завершение выполнения метода здесь приравнивается к выходу из потока. Или даже вообще просто убрать эту строчку, так как дальше кода нет. Метод и так завершится.

Как это работает:

static void Main(string[] args)
{
    Thread thread = new Thread(Method);
    thread.Start();
    Console.WriteLine(thread.IsAlive);
    Thread.Sleep(1000);
    Console.WriteLine(thread.IsAlive);
}

static void Method() { Thread.Sleep(500); }

Вывод в консоль

True
False

То есть поток автоматически завершается как только код, который был запущен в этом потоке, завершает работу.

Еще советую никогда не использовать Thread.Abort(), так как это может привести к нестабильности приложения.

Abort() не гарантирует завершение потока. Вот пример, как это воспроизвести, данный поток невозоможно завершить. Так же, так как он не является фоновым, приложение так же невозможно завершить. То есть единственый способ завершения - это принудительно остановить отладку или закрыть окно консоли, а если Winforms, то и вовсе придется убивать процесс через Диспетчер задач. Не используйте Abort().

// ПЛОХОЙ КОД, НЕ ДЕЛАЙТЕ ТАК!
static void Main(string[] args)
{
    Thread thread = new Thread(Method);
    thread.Start();
    Thread.Sleep(50);
    thread.Abort(); // не работает
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(thread.IsAlive);
        Thread.Sleep(1000);
    }
}

static void Method() { try { Thread.Sleep(500); } finally { while (true) { Thread.Sleep(1000); } } }

True
True
True
True
True
True
True
True
True
True

То есть все что вам надо сделать - это завершить запущенный в потоке код. Обратите внимание на бесконечную рекурсию StartListener() -> Listen() -> OnNotificationComplete() -> GetData() -> StartListener() -> Listen() -> ...и далее по кругу до бесконечности, вам нужно разорвать это кольцо. Вызывая Abort() вы останавливаете только один поток, но на тот момент у вас уже запущена большая куча таких потоков, а в переменной сохранен только последний. Это работает как цепная реакция Thread1 -> UI -> Thread2 -> UI -> Thread3 -> UI -> ..., и каждый раз при перезапуске StartListener, он перезаписывает переменную listener, теряя связь с тем потоком, ссылка на который там хранилась.

Посмотрите этот пример:

aepot
  • 49,560