2019-07-22 22:17:46 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2019-11-05 04:14:31 +00:00
|
|
|
|
using System.Reactive.Linq;
|
|
|
|
|
using System.Reactive.Subjects;
|
2019-07-22 22:17:46 +00:00
|
|
|
|
using System.Threading;
|
2019-12-04 01:26:26 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2019-12-04 04:12:08 +00:00
|
|
|
|
using Wabbajack.Common.StatusFeed;
|
2019-07-22 22:17:46 +00:00
|
|
|
|
|
|
|
|
|
namespace Wabbajack.Common
|
|
|
|
|
{
|
2019-11-20 23:39:03 +00:00
|
|
|
|
public class WorkQueue : IDisposable
|
2019-07-22 22:17:46 +00:00
|
|
|
|
{
|
2019-12-04 01:26:26 +00:00
|
|
|
|
internal BlockingCollection<Func<Task>>
|
|
|
|
|
Queue = new BlockingCollection<Func<Task>>(new ConcurrentStack<Func<Task>>());
|
2019-07-22 22:17:46 +00:00
|
|
|
|
|
2019-12-13 00:40:21 +00:00
|
|
|
|
public const int UnassignedCpuId = 0;
|
2019-12-09 00:19:36 +00:00
|
|
|
|
|
2019-12-13 00:40:21 +00:00
|
|
|
|
private static readonly AsyncLocal<int> _cpuId = new AsyncLocal<int>();
|
|
|
|
|
public int CpuId => _cpuId.Value;
|
2019-07-22 22:17:46 +00:00
|
|
|
|
|
2019-12-23 04:42:57 +00:00
|
|
|
|
public static bool WorkerThread => AsyncLocalCurrentQueue.Value != null;
|
|
|
|
|
public bool IsWorkerThread => WorkerThread;
|
2019-12-07 06:40:48 +00:00
|
|
|
|
internal static readonly AsyncLocal<WorkQueue> AsyncLocalCurrentQueue = new AsyncLocal<WorkQueue>();
|
2019-09-14 04:35:42 +00:00
|
|
|
|
|
2019-12-03 23:03:47 +00:00
|
|
|
|
private readonly Subject<CPUStatus> _Status = new Subject<CPUStatus>();
|
2019-11-17 04:16:42 +00:00
|
|
|
|
public IObservable<CPUStatus> Status => _Status;
|
2019-11-17 06:02:09 +00:00
|
|
|
|
|
2019-12-07 02:05:50 +00:00
|
|
|
|
public List<Thread> Threads { get; private set; }
|
2019-08-10 15:21:50 +00:00
|
|
|
|
|
2019-12-04 00:03:43 +00:00
|
|
|
|
private CancellationTokenSource _cancel = new CancellationTokenSource();
|
|
|
|
|
|
2019-12-09 00:19:36 +00:00
|
|
|
|
// This is currently a lie, as it wires to the Utils singleton stream This is still good to have,
|
|
|
|
|
// so that logic related to a single WorkQueue can subscribe to this dummy member so that If/when we
|
|
|
|
|
// implement log messages in a non-singleton fashion, they will already be wired up properly.
|
|
|
|
|
public IObservable<IStatusMessage> LogMessages => Utils.LogMessages;
|
|
|
|
|
|
2019-11-17 23:48:32 +00:00
|
|
|
|
public WorkQueue(int threadCount = 0)
|
2019-07-22 22:17:46 +00:00
|
|
|
|
{
|
2019-11-17 23:48:32 +00:00
|
|
|
|
StartThreads(threadCount == 0 ? Environment.ProcessorCount : threadCount);
|
2019-07-22 22:17:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-17 23:48:32 +00:00
|
|
|
|
private void StartThreads(int threadCount)
|
2019-07-22 22:17:46 +00:00
|
|
|
|
{
|
2019-11-20 23:39:03 +00:00
|
|
|
|
ThreadCount = threadCount;
|
2019-12-13 00:40:21 +00:00
|
|
|
|
Threads = Enumerable.Range(1, threadCount)
|
2019-09-14 04:35:42 +00:00
|
|
|
|
.Select(idx =>
|
|
|
|
|
{
|
2019-12-04 01:26:26 +00:00
|
|
|
|
var thread = new Thread(() => ThreadBody(idx).Wait());
|
2019-09-14 04:35:42 +00:00
|
|
|
|
thread.Priority = ThreadPriority.BelowNormal;
|
|
|
|
|
thread.IsBackground = true;
|
|
|
|
|
thread.Name = string.Format("Wabbajack_Worker_{0}", idx);
|
|
|
|
|
thread.Start();
|
|
|
|
|
return thread;
|
|
|
|
|
}).ToList();
|
2019-07-22 22:17:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-20 23:39:03 +00:00
|
|
|
|
public int ThreadCount { get; private set; }
|
|
|
|
|
|
2019-12-04 01:26:26 +00:00
|
|
|
|
private async Task ThreadBody(int idx)
|
2019-07-22 22:17:46 +00:00
|
|
|
|
{
|
2019-12-13 00:40:21 +00:00
|
|
|
|
_cpuId.Value = idx;
|
2019-12-07 06:40:48 +00:00
|
|
|
|
AsyncLocalCurrentQueue.Value = this;
|
2019-07-22 22:17:46 +00:00
|
|
|
|
|
2019-12-04 00:03:43 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
Report("Waiting", 0, false);
|
2019-12-04 01:26:26 +00:00
|
|
|
|
if (_cancel.IsCancellationRequested) return;
|
2019-12-14 05:46:20 +00:00
|
|
|
|
Func<Task> f;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
f = Queue.Take(_cancel.Token);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
throw new OperationCanceledException();
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-04 01:26:26 +00:00
|
|
|
|
await f();
|
2019-12-04 00:03:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException)
|
2019-07-22 22:17:46 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-14 04:35:42 +00:00
|
|
|
|
|
2019-11-28 17:30:19 +00:00
|
|
|
|
public void Report(string msg, int progress, bool isWorking = true)
|
2019-07-22 22:17:46 +00:00
|
|
|
|
{
|
2019-11-17 04:16:42 +00:00
|
|
|
|
_Status.OnNext(
|
|
|
|
|
new CPUStatus
|
|
|
|
|
{
|
|
|
|
|
Progress = progress,
|
2019-11-28 17:30:19 +00:00
|
|
|
|
ProgressPercent = progress / 100f,
|
2019-11-17 04:16:42 +00:00
|
|
|
|
Msg = msg,
|
2019-12-13 00:40:21 +00:00
|
|
|
|
ID = _cpuId.Value,
|
2019-11-28 17:30:19 +00:00
|
|
|
|
IsWorking = isWorking
|
2019-11-17 04:16:42 +00:00
|
|
|
|
});
|
2019-07-22 22:17:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-04 01:26:26 +00:00
|
|
|
|
public void QueueTask(Func<Task> a)
|
2019-07-22 22:17:46 +00:00
|
|
|
|
{
|
|
|
|
|
Queue.Add(a);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-07 02:04:42 +00:00
|
|
|
|
public void Dispose()
|
2019-08-02 23:04:04 +00:00
|
|
|
|
{
|
2019-12-04 00:03:43 +00:00
|
|
|
|
_cancel.Cancel();
|
2019-11-20 23:39:03 +00:00
|
|
|
|
Queue?.Dispose();
|
|
|
|
|
}
|
2019-07-22 22:17:46 +00:00
|
|
|
|
}
|
2019-11-05 04:14:31 +00:00
|
|
|
|
|
|
|
|
|
public class CPUStatus
|
|
|
|
|
{
|
|
|
|
|
public int Progress { get; internal set; }
|
2019-11-28 17:30:19 +00:00
|
|
|
|
public float ProgressPercent { get; internal set; }
|
2019-11-05 04:14:31 +00:00
|
|
|
|
public string Msg { get; internal set; }
|
|
|
|
|
public int ID { get; internal set; }
|
2019-11-28 17:30:19 +00:00
|
|
|
|
public bool IsWorking { get; internal set; }
|
2019-11-05 04:14:31 +00:00
|
|
|
|
}
|
2019-11-20 23:39:03 +00:00
|
|
|
|
}
|