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

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

Кстати метод Abort предназначен для аварийного завершения потока, в случае ошибок и т.п. В нормальных ситуациях поток обязан сам завершаться!
можно ли сделать без потока?
не только можно, но и нужно, тут вообще поток прикручен как собаке пятая нога, да еще и с Suspend/Resume - это просто ужас!
если метод будет не такой простой? Что если в нём будет много кода
если в методе много кода значит это плохой код - его нужно переделывать так, чтобы в методе было мало кода
гнило оттого что в методе много кода, его надо рефакторить
Я думал есть каой-то хороший способ, предусмотренный классом Thread.
Никаких хороших способов о которых ты думал ни в одном языке и в теории не предусмотрено. Просто ты не сталкивался еще с многопоточными алгоритмами. Для управления потоками используются специальные механизмы синхронизации потоков - семафоры, критические секции, мейлбоксы и т.п.
Когда ты ознакомишься с этим, поймешь добавляя всего один поток ты очень сильно усложнишь код, вплоть до того что простейшие конструкции прийдется обдумывать днями... Нужно тебе это? Иногда это выгодно с точки зрения производительности, но - иногда! Случаев когда нужно использовать потоки не так уж и много.
Про GC я вот что скажу: между закрытием формы и вызовом GC всегда будет проходить достаточно времени, чтобы поток успел вызвать FillEllipse. И на закрытой форме это даёт исключение. Так что пусть GC идёт пока что лесом.
у тебя изначально неправильный алгоритм рисования, во первых рисовать нужно из метода OnPaint, а не из другого потока. Во вторых рисовать нужно на Graphics который предается в OnPaint, а не создавать его с помощью CreateGraphics.
Если ты не будешь соблюдать два этих правила, то Windows не гарантирует, что изображение будет отображено корректно, более того оно будет неприятно мерцать и жрать кучу лишнего процессорного времени
Чтобы правильно рисовать тебе нужно создать некую логическую структуру, которая будет описывать что именно нужно рисовать, после обновления этой структуры нужно вызвать метод Invalidate() для объекта который нужно перерисовать. Это проинформирует операционную систему что часть окна нужно перерисовать, и если эта часть окна видима, то система вызовет событие OnPaint. В аргументах будет Graphics на котором нужно нарисовать содержимое окна, в обработчике этого события ты и отрисовываешь все что нужно согласно логической структуры.
Тут важно понять что решение когда нужно рисовать должна принимать ОС, не нужно рисовать когда ОС об этом не попросила
Впринципе ты можешь обновлять логическую структуру из отдельного потока, синхронизируя доступ к ней через критическую секцию, но я думаю в твоем случае это не нужно и только добавит кучу лишнего кода, усложнив логику.
Возможно тебя удивит, например игры делают как правило в одном потоке - все операции производятся в одном потоке!

Сейчас появлись игры поддерживающие многопроцессорные системы, но дополнительные потоки используются только для специфических вещей - расчет физики, AI и т.п. Все остальное все равно в одном потоке происходит.