Код глючит, когда отваливается интернет

Задача такая: каждые 5 секунд проверять твич на наличие стрима. Если стрим есть - запустить дампер.
Загрузка списка каналов:

        private void LoadList(string filePath)
        {
            JArray jArray = JArray.Parse(File.ReadAllText(filePath));
            foreach (JObject j in jArray)
            {
                string channelName = j.Value<string>("channelName");
                StreamItem streamItem = new StreamItem(channelName);
                streamItem.CopiesCount = j.Value<int>("copiesCount");
                if (streamItem.CopiesCount > 3)
                {
                    streamItem.CopiesCount = 3;
                }
                else if (streamItem.CopiesCount < 0)
                {
                    streamItem.CopiesCount = 0;
                }
                streamItem.IsImportant = j.Value<bool>("important");

                streamItem.TimerRemaining = config.CheckingIntervalInactive;
                AddStreamToListView(streamItem);
            }
        }

        private void AddStreamToListView(StreamItem streamItem)
        {
            ListViewItem item = new ListViewItem(streamItem.ChannelName);
            item.SubItems.Add(timerCheck.Enabled ? streamItem.TimerRemaining.ToString() : "Отключен!");
            item.SubItems.Add(streamItem.DumpingFilePath);
            string sizeString = streamItem.DumpingFileSize >= 0L ? streamItem.DumpingFileSize.ToString() : null;
            item.SubItems.Add(sizeString);
            item.SubItems.Add(streamItem.CopiesCount.ToString());
            item.SubItems.Add(string.Empty);
            item.SubItems.Add(string.Empty);
            item.Tag = streamItem;
            listViewStreams.Items.Add(item);
        }
        private void ResetItem(StreamItem streamItem)
        {
            streamItem.DumpingFilePath = null;
            streamItem.DumpingFileSize = -1L;
            streamItem.DateServer = DateTime.MinValue;
            streamItem.DateLocal = DateTime.MinValue;
            streamItem.TimerRemaining = config.CheckingIntervalInactive;
            int id = FindStreamItemInListView(streamItem, listViewStreams);
            if (id >= 0)
            {
                listViewStreams.Items[id].SubItems[COLUMN_ID_FILEPATH].Text = null;
                listViewStreams.Items[id].SubItems[COLUMN_ID_FILESIZE].Text = null;
                listViewStreams.Items[id].SubItems[COLUMN_ID_DATE].Text = null;
                listViewStreams.Items[id].SubItems[COLUMN_ID_PLAYLIST_URL] = null;
            }
        }

        private void CheckItem(int itemIndex)
        {
            StreamItem streamItem = (StreamItem)listViewStreams.Items[itemIndex].Tag;
            if (!streamItem.IsChecking)
            {
                streamItem.IsChecking = true;
                listViewStreams.Items[itemIndex].SubItems[COLUMN_ID_TIMER].Text = "Запуск проверки...";
                MakeThread(streamItem).Start(SynchronizationContext.Current);
            }
        }

        private void CheckAllItems()
        {
            for (int i = 0; i < listViewStreams.Items.Count; i++)
            {
                CheckItem(i);
            }
        }

        private void timerCheck_Tick(object sender, EventArgs e)
        {
            for (int i = 0; i < listViewStreams.Items.Count; i++)
            {
                StreamItem streamItem = (StreamItem)listViewStreams.Items[i].Tag;
                if (!streamItem.IsChecking)
                {
                    streamItem.TimerRemaining--;
                    listViewStreams.Items[i].SubItems[COLUMN_ID_TIMER].Text = streamItem.TimerRemaining.ToString();
                    if (streamItem.TimerRemaining <= 0)
                    {
                        streamItem.IsChecking = true;
                        listViewStreams.Items[i].SubItems[COLUMN_ID_TIMER].Text = "Запуск проверки...";
                        streamItem.TimerRemaining = streamItem.IsStreamActive ? config.CheckingIntervalActive : config.CheckingIntervalInactive;

                        MakeThread(streamItem).Start(SynchronizationContext.Current);
                    }
                }
            }
        }

Запуск потока и обработка событий:

        private Thread MakeThread(StreamItem streamItem)
        {
            ThreadChecker threadChecker = new ThreadChecker(streamItem);
            threadChecker.CheckingStarted += OnCheckingStarted;
            threadChecker.NewLiveDetected += OnLiveDetected;
            threadChecker.DumpingStarted += OnDumpingStarted;
            threadChecker.FileSizeChanged += OnFileSizeChanged;
            threadChecker.DumpingHalted += OnDumpingHalted;
            threadChecker.TitleChanged += OnTitleChanged;
            threadChecker.Completed += OnThreadCheckCompleted;
            threadChecker.LogAdding += OnLogAdding;

            return new Thread(threadChecker.Work);
        }

        private void OnCheckingStarted(object sender)
        {
            StreamItem streamItem = (sender as ThreadChecker).StreamItem;
            int id = FindStreamItemInListView(streamItem, listViewStreams);
            if (id >= 0)
            {
                listViewStreams.Items[id].SubItems[COLUMN_ID_TIMER].Text = "Проверка...";
            }
        }

        private void OnLiveDetected(object sender)
        {
            StreamItem streamItem = (sender as ThreadChecker).StreamItem;
            notifyIcon1.BalloonTipTitle = "СТРИИИИИИМ!!!!!!!";
            notifyIcon1.BalloonTipText = $"Канал {streamItem.ChannelName} начал трансляцию!";
            notifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
            notifyIcon1.ShowBalloonTip(5000);

            AddToLog(streamItem.ChannelName, "Новая трансляция!");
        }

        private void OnDumpingStarted(object sender, int errorCode)
        {
            StreamItem streamItem = (sender as ThreadChecker).StreamItem;
            int id = FindStreamItemInListView(streamItem, listViewStreams);
            if (id >= 0)
            {
                if (errorCode == 200)
                {
                    listViewStreams.Items[id].SubItems[COLUMN_ID_FILEPATH].Text = streamItem.DumpingFilePath;
                    listViewStreams.Items[id].SubItems[COLUMN_ID_FILESIZE].Text = "0";
                    listViewStreams.Items[id].SubItems[COLUMN_ID_DATE].Text = streamItem.DateLocal.ToString();
                    listViewStreams.Items[id].SubItems[COLUMN_ID_PLAYLIST_URL].Text = streamItem.PlaylistUrl;
                }
                else
                {
                    notifyIcon1.BalloonTipTitle = streamItem.ChannelName;
                    notifyIcon1.BalloonTipText = "Ошибка доступа к плейлисту!";
                    notifyIcon1.BalloonTipIcon = ToolTipIcon.Error;
                    notifyIcon1.ShowBalloonTip(5000);

                    AddToLog(streamItem.ChannelName, $"Ошибка доступа к плейлисту! Код ошибки: {errorCode}");

                    ResetItem(streamItem);
                }
            }
        }

        private void OnFileSizeChanged(object sender, long oldFileSize)
        {
            StreamItem streamItem = (sender as ThreadChecker).StreamItem;
            int id = FindStreamItemInListView(streamItem, listViewStreams);
            if (id >= 0)
            {
                listViewStreams.Items[id].SubItems[COLUMN_ID_FILESIZE].Text = FormatSize(streamItem.DumpingFileSize);
            }
        }

        private void OnDumpingHalted(object sender)
        {
            StreamItem streamItem = (sender as ThreadChecker).StreamItem;
            int id = FindStreamItemInListView(streamItem, listViewStreams);
            if (id >= 0)
            {
                notifyIcon1.BalloonTipTitle = streamItem.ChannelName;
                notifyIcon1.BalloonTipText = "ЗАВИСЛО!!!";
                notifyIcon1.BalloonTipIcon = ToolTipIcon.Error;
                notifyIcon1.ShowBalloonTip(5000);

                AddToLog(streamItem.ChannelName, "Зависло нафиг!");

                ResetItem(streamItem);

                MakeThread(streamItem).Start(SynchronizationContext.Current);
            }
        }

        private void OnTitleChanged(object sender)
        {
            StreamItem streamItem = (sender as ThreadChecker).StreamItem;
            notifyIcon1.BalloonTipTitle = streamItem.ChannelName;
            notifyIcon1.BalloonTipText = $"Изменилось название: {streamItem.Title}";
            notifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
            notifyIcon1.ShowBalloonTip(5000);

            AddToLog(streamItem.ChannelName, $"Новое название стрима: {streamItem.Title}");
        }

        private void OnThreadCheckCompleted(object sender, int errorCode)
        {
            StreamItem streamItem = (sender as ThreadChecker).StreamItem;
			int id = FindStreamItemInListView(streamItem, listViewStreams);
			if (id >= 0) //это срабатывает
			{
				if (errorCode == 200)
				{
					listViewStreams.Items[id].SubItems[COLUMN_ID_FILEPATH].Text = streamItem.DumpingFilePath;
					listViewStreams.Items[id].SubItems[COLUMN_ID_PLAYLIST_URL].Text = streamItem.PlaylistUrl;

					streamItem.TimerRemaining = config.CheckingIntervalActive;
				}
				else
				{
					listViewStreams.Items[id].SubItems[COLUMN_ID_FILEPATH].Text =
						errorCode != TwitchApi.ERROR_USER_OFFLINE ?
						$"Ошибка! {TwitchApi.ErrorCodeToString(errorCode)}, {(sender as ThreadChecker).LastErrorMessage}" : null;
					listViewStreams.Items[id].SubItems[COLUMN_ID_FILESIZE].Text = null;
					listViewStreams.Items[id].SubItems[COLUMN_ID_DATE].Text = null;
					listViewStreams.Items[id].SubItems[COLUMN_ID_PLAYLIST_URL].Text = null;
					if (errorCode == TwitchApi.ERROR_USER_NOT_FOUND)
					{
						streamItem.TimerRemaining = (int)numericUpDownTimerIntervalActive.Maximum;
					}
				}
				listViewStreams.Items[id].SubItems[COLUMN_ID_TIMER].Text =
					timerCheck.Enabled ? streamItem.TimerRemaining.ToString() : "Отключен!"; //а это уже нет
			}
            streamItem.IsChecking = false; //и это, видимо, тоже нет.
        }

        private void OnLogAdding(object sender, string logText)
        {
            AddToLog((sender as ThreadChecker).StreamItem.ChannelName, logText);
        }

Проблема в том, что всё нормально работает. Но бывает, что у меня на пару секунд отваливается интернет. Соединение как-бы держится, но ничего не передаётся. В результате, в одном из потоков метод OnThreadCheckCompleted не выполняется до конца и во втором столбе ListView постоянно висит надпись “Проверка”. Если попытаться обратиться к этому объекту в Tag, то

        private void miEditChannelToolStripMenuItem_Click(object sender, EventArgs e)
        {
            int id = listViewStreams.SelectedIndex(); //тут NullPointerException
            if (id >= 0)
            {
                StreamItem streamItem = (StreamItem)listViewStreams.Items[id].Tag;
                FormAdding formAdding = new FormAdding(streamItem);
                if (formAdding.ShowDialog() == DialogResult.OK)
                {
                    listViewStreams.Items[id].Tag = formAdding.StreamItem;
                    listViewStreams.Items[id].SubItems[COLUMN_ID_COPIES_COUNT].Text =
                        formAdding.StreamItem.CopiesCount.ToString();
                }
            }
        }

        public static int SelectedIndex(this ListView listView) //в отдельном классе
        {
            return listView.Items.Count > 0 && listView.SelectedIndices.Count > 0 ? listView.SelectedIndices[0] : -1;
        }

код выполняется и уже после этого возникает NullPointerException. Но как такое может быть? :thinking:

Может это из-за того, что я в качестве пустой строки присваиваю null? :thinking:
Если делать через try, то потом ошибка вылезает в другом месте. Очень похоже на низкоуровневый сбой памяти. Ошибку пишет в одном месте, а на самом деле её причина совершенно в другом. На Delphi у меня такое часто было.

:man_facepalming:
Нашёл ошибку!
listViewStreams.Items[id].SubItems[COLUMN_ID_PLAYLIST_URL] = null;
Я забыл .Text дописать :man_facepalming: Ну ёмоё!