Conversation
|
|
||
| using NUnit.Framework; | ||
|
|
||
| public class Tests |
There was a problem hiding this comment.
А ещё линтер очень недоволен, и тут даже по делу
| [Test] | ||
| public void Test1() | ||
| { | ||
| Assert.Pass(); |
| @@ -0,0 +1,62 @@ | |||
| namespace MyThreadPool; | |||
|
|
|||
| public class MyTask<T> : IMyTask<T> | |||
There was a problem hiding this comment.
- Надо комментарии
- Это лучше сделать private-вложенным классом ThreadPool-а, чтобы извне нельзя было делать странные вещи, например, вызывать Start
| /// <inheritdoc/> | ||
| public bool IsCompleted { get; private set; } = false; | ||
|
|
||
| public MyTask(Func<T> func, MyThreadPool threadPool) |
There was a problem hiding this comment.
Тут nullability-анализ совсем недоволен
| private Func<T> func; | ||
| private ManualResetEvent reset = new(false); | ||
| private T result; | ||
| private Exception? retrunedException; |
| { | ||
| if (!this.IsCompleted) | ||
| { | ||
| this.reset.WaitOne(); |
There was a problem hiding this comment.
Это прямо нарушает требование условия про неблокирование вызывающего
| while (this.tasks.Count > 0) | ||
| { | ||
| } |
There was a problem hiding this comment.
Крутящееся ожидание нельзя, процессор сожжёте :)
| throw new AggregateException(this.retrunedException); | ||
| } | ||
|
|
||
| return this.result; |
There was a problem hiding this comment.
Что-то я не понял, как в result в итоге оказывается значение
| { | ||
| while (!token.IsCancellationRequested) | ||
| { | ||
| if (this.collection.TryTake(out var action)) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
yurii-litvinov
left a comment
There was a problem hiding this comment.
Существенно лучше, чем было, но существенно хуже, чем могло бы быть
| for (int i = 0; i < 20 && this.tasks.Count > 0; ++i) | ||
| { | ||
| Thread.Sleep(1000); | ||
| } |
There was a problem hiding this comment.
Так оно заблокирует вызывающего на целую секунду минимум, так нельзя. Рабочие потоки могут сами сообщать, что они всё, например, выставляя какой-нибудь AutoResetEvent, который мы тут можем ждать. И параллельно с этим запустить поток, висящий на ThreadSleep 20 секунд, и тоже выставляющий Event — типа таймаут. В реальной жизни это был бы Task.WhenAny, но в этой задаче Task.WhenAny нельзя — слишком высокоуровневый.
| } | ||
| else | ||
| { | ||
| Thread.Sleep(1000); |
There was a problem hiding this comment.
Секунда для процессора — это целая вечность. Типа "пытаемся взять задачу, а если она не нашлась, засыпаем на эпохи, пока звёзды не займут правильное положение". И нет, тут Sleep не надо, мы можем при добавлении задачи в очередь будить поток — используйте ResetEvent-ы.
| { | ||
| private MyThreadPool pool; | ||
| private Func<T> func; | ||
| private ManualResetEvent reset = new(false); |
There was a problem hiding this comment.
Это прежде всего не reset, а event, так что и называться должен так, чтобы отражать событие, которое представляет.
| { | ||
| this.reset.Set(); | ||
| this.IsCompleted = true; | ||
| } |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| this.reset.Set(); | ||
| this.IsCompleted = true; |
There was a problem hiding this comment.
Наоборот. Иначе будет задача, которая уже вернула результат, но ещё не IsCompleted, это не обязательно баг, но нездорово
| /// <inheritdoc/> | ||
| public IMyTask<TNewResult> ContinueWith<TNewResult>(Func<T, TNewResult> func1) | ||
| { | ||
| return this.pool.Submit(() => func1(this.Result)); |
There was a problem hiding this comment.
Не-а. Если есть длинная задача, у которой 150 Continuation-ов, то они все попадут в пул и будут ждать, пока задача закончится (встав на ResetEvent в Result). Остальные задачи в пуле не смогут считаться, потому что потоки пула заняты. Надо, чтобы Continuation-ы попадали в пул только когда они готовы считаться.
|
|
||
| pool.Shutdown(); | ||
| } | ||
| } No newline at end of file |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
yurii-litvinov
left a comment
There was a problem hiding this comment.
Ещё пару важных моментов всё-таки надо поправить
| }); | ||
|
|
||
| timeoutThread.Start(); | ||
| this.tasksOver.WaitOne(); |
There was a problem hiding this comment.
tasksOver выставляется каждым рабочим потоком, когда он видит пустую очередь (например, в самом начале) и нигде не сбрасывается вроде. Если это так, то тут мы сразу же проскочим этот WaitOne, вне зависимости от занятости потоков.
| { | ||
| var task = new MyTask<TResult>(func, this); | ||
| this.tasks.Add(() => task.Start()); | ||
| this.taskAdded.Set(); |
There was a problem hiding this comment.
taskAdded — это AutoResetEvent, так что если мы вызвали десять раз Submit до активации какого-то из рабочих потоков, у нас будет десять задач в очереди и ровно один поток, который разбужен, чтобы их делать. Не очень хорошо.
| } | ||
|
|
||
| var newTask = new MyTask<TNewResult>(NewFunc, this.pool); | ||
| var thread = new Thread(() => newTask.Start()); |
There was a problem hiding this comment.
А так continuation запускается вообще в отдельном потоке, что, конечно, разгружает тредпул, но убивает всю идею тредпула :) Нет, так нельзя, надо класть на пул, но потом, по завершению основной задачи.
| private bool isShutdown; | ||
| private AutoResetEvent tasksOver; | ||
| private AutoResetEvent taskAdded; | ||
| private Semaphore taskCount = new Semaphore(0, 10000); |
There was a problem hiding this comment.
Почему 10000? Больше 10000 пул не принимает? Нет, пул должен принимать сколько угодно задач. Можно без семафора, на Event-ах :)
| for (int i = 0; i < this.ThreadCount; i++) | ||
| { | ||
| this.taskAdded.Set(); | ||
| this.taskCount.Release(); |
There was a problem hiding this comment.
Release имеет аргумент "сколько зарелизить", так что можно было и не в цикле
| var task = new MyTask<TResult>(func, this); | ||
| this.tasks.Add(() => task.Start()); | ||
| this.taskAdded.Set(); | ||
| this.tasksOver.Reset(); |
There was a problem hiding this comment.
Это не синхронизировано с Set в рабочем потоке, поэтому, кажется, может произойти "Рабочий поток увидел, что в очереди пусто, приготовился сделать tasksOver.Set, но тут произошло переключение контекстов, управление передалось в Submit, он сделал this.tasksOver.Reset();, управление вернулось в рабочий поток, он тут же сделал Set и задача в очереди, но задачи типа кончились."
| return this.pool.Submit(() => func1(this.Result)); | ||
| } | ||
|
|
||
| MyTask<TNewResult> newTask = new MyTask<TNewResult>(() => func1(this.Result), this.pool); |
There was a problem hiding this comment.
Два раза один тип в одной строке не пишут :)
| { | ||
| this.deferredTasks.Add(() => newTask.Start()); | ||
| } | ||
| catch (InvalidOperationException) |
There was a problem hiding this comment.
Вполне реалистичная ситуация, когда ContinueWith вызвался одновременно с окончанием Start. Попробуйте без исключений это разрешить (можно lock использовать -- Вы, я заметил, придумываете жуткие извращения, чтобы не писать lock). Исключения не должны бросаться, когда всё нормально.
| this.deferredTasks.CompleteAdding(); | ||
| foreach (var task in this.deferredTasks) | ||
| { | ||
| this.pool.Submit(() => task); |
There was a problem hiding this comment.
А если к этому моменту пулу сделали Shutdown, Submit бросит исключение --- в рабочий поток, так что пользователь ничего про него не узнает. А рабочий поток бесславно сдохнет. Нехорошо.
Simple thread pool