我一个接一个地运行许多时间\CPU密集型进程(TimeExpensive类型(。主线程 (A( 异步启动另一个线程 (B( 中的 TimeExpensive 进程,并变为非活动状态。完成时,线程 B 同步触发调用方的完成处理程序,并在线程 B 中启动下一个 TimeExpensive 进程。创建一个新线程 (C(,但在启动 C 后,B 完成。因此,对于 n 个进程,会创建 n 个线程,并且大多数情况下,它们不会共存。
人们可能希望它以线性单线程方式实现,但TimeExpensiveing是由第三方实现的,当它运行时,它使用所有系统内核并运行数小时。
//This will run as console app
class Program
{
static void Main(string[] args)
{
new Program().StartJobs();
}
void StartJobs()
{
Main mainJob = new Main();
mainJob.MainCompletionEvent +=
new Action<object, EventArgs>(mainJob_MainCompletionEvent);
mainJob.Start();
}
void mainJob_MainCompletionEvent(object sender, EventArgs e)
{
//if(success) Environment.Exit(0);
}
}
class Main
{
int processCounter = 0;
public event Action<object, EventArgs> MainCompletionEvent;
public void Start()
{
//...do other important tasks here...
processCounter++;
TimeExpensive te = new TimeExpensive();
te.CompletionEvent += new Action(TimeExpensive_CompletionHandler);
Thread aThread = new Thread(te.Run);
aThread.IsBackground = false;
aThread.Name = "TimeExpensive Thread: " + processCounter;
aThread.Start();
}
void TimeExpensive_CompletionHandler()
{
Console.WriteLine("current Thread Name: " + Thread.CurrentThread.Name);
//Start another Process In Background if
if (processCounter < 5)
{
Start();
}
else
{
Console.ReadKey();
if (JobCompletionEvent != null)
JobCompletionEvent(this, new EventArgs());
}
}
}
class TimeExpensive
{
public event Action CompletionEvent;
public void Run()
{
//doing time expensive task
//...
//when finish Notify completion Handler...
if (CompletionEvent != null)
{
CompletionEvent();
}
}
}
//Output
current Thread Name: TimeExpensive Thread: 1
current Thread Name: TimeExpensive Thread: 2
current Thread Name: TimeExpensive Thread: 3
current Thread Name: TimeExpensive Thread: 4
current Thread Name: TimeExpensive Thread: 5
上面的实现模仿了我描述的行为。困扰我的是事件处理程序同步运行,直到下一个线程启动,在此期间,它正在执行许多不是为它设计的任务。
不确定这是否好,有没有办法在线程 B 的完成处理程序中返回线程 A? 或者我是否应该最好使用另一个委托启动事件处理程序执行。开始调用?
我希望用简单而安全的方法做到这一点。任何帮助将不胜感激。
附言我读了很多帖子,但没有人很好地处理这种情况。
编辑
添加了静态主线程以显示如何在控制台应用中启动此代码。 请记住,还可以创建UI来启动"主要"作业。它肯定会创建BackgroundWorker 线程来创建 mainJob 对象并运行它。谢谢!
有没有办法在线程 B 的完成处理程序中返回线程 A?
不,您没有管道来将调用从一个线程封送到另一个线程。 这种管道由 GUI 应用程序的主线程提供。 出于必要,用户界面从根本上来说是线程不安全的。 UI 线程有多个支持此类封送处理的实现详细信息。 它的行为类似于生产者/使用者线程模型的典型实现中的使用者。
这需要一个线程安全队列和一个在使用者中读取队列的循环。 您可能会将其识别为 Windows GUI 应用程序中的消息队列。 使用调用 GetMessage 的消息循环来读取通知并对其执行操作。 封送调用现在很简单,您只需将消息发布到消息队列,UI 线程读取它并执行请求。 发布由 Control.BeginInvoke for Winforms 和 Dispatcher.BeginInvoke for WPF 实现。
您当然可以自己实现此同步机制。 .NET 4 BlockingCollection 类使它变得简单。 但请记住,您必须从根本上改变线程 A 的执行方式。 保持对发布到队列的请求的响应非常重要。 像 BackgroundWorker 这样的类试图解决的问题。 请记住,GUI 消息循环之所以存在,是因为它是必要的,UI 不是线程安全的。 控制台应用(通常(没有相同类型的负担,控制台是线程安全的。
您遇到的问题是由于正确执行线程处理的难度。我用演员编码了你的例子:
type Actor<'a> = MailboxProcessor<'a>
type SupMsg = WaitForDone of AsyncReplyChannel<string>
type ProgramState = RunNumber of int * Actor<WorkerMsg> option
and WorkerMsg = Begin of Id * AsyncReplyChannel<string>
and Id = int
let startComputation () = Actor.Start(fun inbox ->
async {
let! Begin(id, chan) = inbox.Receive()
printfn "Running Computation"
do! Async.Sleep(20) // zZz
chan.Reply(sprintf "'%i is done!'" id) })
let sup () = Actor.Start(fun inbox ->
let rec loop state =
async {
match state with
| RunNumber(_, None) -> return! loop <| RunNumber(1, Some(startComputation ()))
| RunNumber(run, Some(active)) ->
let! completed = active.PostAndAsyncReply(fun chan -> Begin(run, chan))
printfn "sup observed: %s" completed
let active' = Some(startComputation ())
if run <> 5 then return! loop <| RunNumber(run + 1, active')
else return! isDone () }
and isDone () =
async {
let! WaitForDone(chan) = inbox.Receive()
return chan.Reply("all done") }
loop <| RunNumber(0, None))
[<EntryPoint>]
let main args =
printfn "%s" <| (sup ()).PostAndReply(fun chan -> WaitForDone(chan))
0
其中作为输出:
> main();;
Running Computation
sup observed: '1 is done!'
Running Computation
sup observed: '2 is done!'
Running Computation
sup observed: '3 is done!'
Running Computation
sup observed: '4 is done!'
Running Computation
sup observed: '5 is done!'
all done
val it : int = 0
如您所见,跨线程通信变得轻而易举。如果您的库是第三方库,则很容易将Async.Sleep(20)
替换为对库的调用。