1 (изменено: Strongest, 2013-08-16 03:52:26)

Тема: AHK: Эмуляция множественных потоков

Так как AHK не поддерживает много потоков одновременно, это очень сужает горизонты.
Хочется выслушать идеи, наработки людей как все таки с эмулировать множественные потоки под управлением «main» скрипта.
Приведу свой пример реализации, но он очень громоздкий, и не удобный.
Хочется выслушать экспертов…

MinScript


Gui, Add, Text, h20 w100 vText, wiat
Gui, Add, Button, Default, Exit
Gui Show    
A := 0
B := 0
C := 0
Sum := 0
IniWrite, 0, test.ini, Script1, A
IniWrite, 0, test.ini, Script2, B
IniWrite, 0, test.ini, Script3, C
Run, Script1.ahk
Run, Script2.ahk
Run, Script3.ahk
Loop
{
    IniRead, A, test.ini, Script1, A, 0
    IniRead, B, test.ini, Script2, B, 0
    IniRead, C, test.ini, Script3, C, 0
    
    Sum := A+B+C
    
    GuiControl,,Text, %A% + %B% + %C% = %Sum%
}
    
ButtonExit:
IniRead, PID1, test.ini, Script1, PID
IniRead, PID2, test.ini, Script2, PID
IniRead, PID3, test.ini, Script3, PID
Process, Close, %PID1%
Process, Close, %PID2%
Process, Close, %PID3%
ExitApp

Script1


Process, Exist
MyPID := ErrorLevel
IniWrite, %MyPid%, test.ini, Script1, PID
SetTimer Start, 2000

Loop
{

}

Start:
Random, A, 0, 100
IniWrite, %A%, test.ini, Script1, A
Return

Script2


SetTimer Start, 2000
Process, Exist
MyPID := ErrorLevel
IniWrite, %myPid%, test.ini, Script2, PID
Loop
{

}

Start:
Random, B, 0, 100
IniWrite, %B%, test.ini, Script2, B
Return

Script3


SetTimer Start, 2000
Process, Exist
MyPID := ErrorLevel
IniWrite, %myPid%, test.ini, Script3, PID
Loop
{

}

Start:
Random, C, 0, 100
IniWrite, %C%, test.ini, Script3, C
Return

2

Re: AHK: Эмуляция множественных потоков

Задача такая, нужно что бы выполнялось несколько операций одновременно, и что бы они не мешали друг другу. Так как в скрипте нельзя запустить несколько потоков типа


SetTime, Lable1, 1000
SetTime, Lable2, 1000
SetTime, Lable3, 1000

Я сделал 4 скрипта: Главный скрипт анализирует работу, и производит манипуляцию с полученных данных от остальных 3 скриптов  которые работают “Одновременно”.
Главный скрипт у меня получает сумму чисел сгенерированных в одно время случайных чисел в 3 остальных скриптах.

3

Re: AHK: Эмуляция множественных потоков

Используйте сообщения.
Естественно скрипты останутся автономны.

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru
Win10x64 v20H2, AutoHotkey_L v1.1.33.09 (Unicode 32-bit). AhkSpy, Hotkey, ClockGui

4 (изменено: Strongest, 2013-08-16 04:04:08)

Re: AHK: Эмуляция множественных потоков

Проблема в том, что не хочется для выполнения 1 задачи использовать много скриптов.
Вот в этом и вопрос, возможно ли сделать как то без лишних запущенных процессов (скриптов).

5 (изменено: Strongest, 2013-08-16 04:57:27)

Re: AHK: Эмуляция множественных потоков

У AHK есть много плюсов относительно C++, то что тут делается в 1 строку, в C++ нужно писать целую функцию.

6

Re: AHK: Эмуляция множественных потоков

Strongest пишет:

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

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

ScriptManage := {}                       ; создаём объект для управления скриптами
ScriptManage.Run := Func("RunScript")    ; создаём несколько методов
ScriptManage.Terminate := Func("TerminateScript")
ScriptManage.Send := Func("SendStringToScript")
OnExit, OnExit

Gui, +LastFound
Gui, Add, Edit, x10 vEdit, Это строчка, которая будет передана дочернему скрипту.
Gui, Add, Button, x+-79 y+10 w80 gButton, Send
Gui, Add, Button, gButton x10 yp wp Default, Run
GuiControl, Focus, Run
Gui, Show, % "y" A_ScreenHeight//3, Это главный скрипт:

Loop
{
   WinSetTitle, % "Это главный скрипт: " mod(A_Index, 100)
   Sleep, 100
}
return


GuiClose:
OnExit:
   for k, v in ScriptManage
      if k is integer
         ScriptManage.Terminate(k)
   ExitApp

Button:
   if (A_GuiControl = "Run")
      ScriptManage.Run(1)   ; единица означает, что запускаем первый скрипт
                            ; у нас он один, но может быть сколько угодно
   if (A_GuiControl = "Terminate")
      ScriptManage.Terminate(1)
   
   if (A_GuiControl = "Send")
      ScriptManage.Send(1)
   
   if (A_GuiControl != "Send")
      GuiControl,, % A_GuiControl, % A_GuiControl = "Run" ? "Terminate" : "Run"
   return

RunScript(this, n)   ; здесь this — вызывающий объект, n — номер скрипта, который будем запускать
{
   ;*********************** это текст скрипта, который будем запускать ************************
   Text1 =  
   (%    ; опция "%", чтобы процент читался как литерал
      #NoTrayIcon 
      Gui, +AlwaysOnTop +ToolWindow
      Gui, Color, Gray
      Gui, Add, Progress, hwndhControl Border Background606060 x20 y20 w350 h50  
      Gui, Show, % "NA w390 h90 y" A_ScreenHeight//3 + 120

      Gui, 2:+Parent%hControl% -Caption +Lastfound
      Gui, 2:Color, 606060
      Gui, 2:Font, s21 cFFAA00, Verdana
      Gui, 2:Add, Text, x350 y6, Можно отправить сюда текст из главного скрипта!
      Gui, 2:Show, NA x0 y0
      WinGetPos,,, Width 

      OnMessage(0xC, "WM_SETTEXT")
      Loop
      { 
          Gui, 2:Show, % "NA y0 x" i := i < -Width ? 0 : i - 1 
          Sleep 10
      }
      return

      GuiClose:
         FileDelete, % A_ScriptFullPath
         ExitApp

      WM_SETTEXT(wp, lp)
      {
         global
         Text := StrGet(lp)
         Gui, 2:New, +Parent%hControl% -Caption +Lastfound
         Gui, 2:Color, 606060
         Gui, 2:Font, s21 cFFAA00, Verdana
         Gui, 2:Add, Text, x350 y6, % Text
         Gui, 2:Show, NA x0 y0
         i := 0
         WinGetPos,,, Width
         return 0
      }
   )
   ;******************************** ; конец текста скрипта ***********************************

   FilePath := A_Temp "\_MyScript_" A_TickCount ".ahk"
   FileAppend, % Text%n%, % FilePath
   Run, % FilePath,,, PID
   this[n] := {Path: FilePath, PID: PID}  ; сохраняем путь к файлу дочернего скрипта и его PID
}                                         ; в соответствующих ключах вызывающего объекта

TerminateScript(this, n)
{
   Critical
   Process, Close, % this[n].PID
   Process, WaitClose, % this[n].PID
   FileDelete, % this[n].Path
}

SendStringToScript(this, n)
{
   GuiControlGet, Text,, Edit1
   SendMessage, WM_SETTEXT := 0xC,, &Text,, % "ahk_pid " this[n].PID
}

Запускаем скрипт, нажимаем Run.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

7

Re: AHK: Эмуляция множественных потоков

Это реально гениально! То что надо! Огромное спасибо!

8 (изменено: teadrinker, 2013-08-16 19:16:31)

Re: AHK: Эмуляция множественных потоков

Кстати, удалять файл дочернего скрипта можно сразу после появления его главного (скрытого) окна. Тогда код будет таким:

ScriptManage := {}                       ; создаём объект для управления скриптами
ScriptManage.Run := Func("RunScript")    ; создаём несколько методов
ScriptManage.Terminate := Func("TerminateScript")
ScriptManage.Send := Func("SendStringToScript")
OnExit, OnExit

Gui, +LastFound
Gui, Add, Edit, x10 vEdit, Это строчка, которая будет передана дочернему скрипту.
Gui, Add, Button, x+-79 y+10 w80 gButton, Send
Gui, Add, Button, gButton x10 yp wp Default, Run
GuiControl, Focus, Run
Gui, Show, % "y" A_ScreenHeight//3, Это главный скрипт:

Loop
{
   WinSetTitle, % "Это главный скрипт: " mod(A_Index, 100)
   Sleep, 100
}
return


GuiClose:
OnExit:
   for k, v in ScriptManage
      if k is integer
         ScriptManage.Terminate(k)
   ExitApp

Button:
   if (A_GuiControl = "Run")
      ScriptManage.Run(1)   ; единица означает, что запускаем первый скрипт
                            ; у нас он один, но может быть сколько угодно
   if (A_GuiControl = "Terminate")
      ScriptManage.Terminate(1)
   
   if (A_GuiControl = "Send")
      ScriptManage.Send(1)
   
   if (A_GuiControl != "Send")
      GuiControl,, % A_GuiControl, % A_GuiControl = "Run" ? "Terminate" : "Run"
   return

RunScript(this, n)   ; здесь this — вызывающий объект, n — номер скрипта, который будем запускать
{
   ;*********************** это текст скрипта, который будем запускать ************************
   Text1 =  
   (
      #NoTrayIcon 
      Gui, +AlwaysOnTop +ToolWindow
      Gui, Color, Gray
      Gui, Add, Progress, hwndhControl Border Background606060 x20 y20 w350 h50  
      Gui, Show, `% "NA w390 h90 y" A_ScreenHeight//3 + 120

      Gui, 2:+Parent`%hControl`% -Caption +Lastfound
      Gui, 2:Color, 606060
      Gui, 2:Font, s21 cFFAA00, Verdana
      Gui, 2:Add, Text, x350 y6, Можно отправить сюда текст из главного скрипта!
      Gui, 2:Show, NA x0 y0
      WinGetPos,,, Width 

      OnMessage(0xC, "WM_SETTEXT")
      Loop
      { 
          Gui, 2:Show, `% "NA y0 x" i := i < -Width ? 0 : i - 1 
          Sleep 10
      }
      return

      GuiClose:
         ExitApp

      WM_SETTEXT(wp, lp)
      {
         global
         Text := StrGet(lp)
         Gui, 2:New, +Parent`%hControl`% -Caption +Lastfound
         Gui, 2:Color, 606060
         Gui, 2:Font, s21 cFFAA00, Verdana
         Gui, 2:Add, Text, x350 y6, `% Text
         Gui, 2:Show, NA x0 y0
         i := 0
         WinGetPos,,, Width
         return 0
      }
   )
   ;******************************** ; конец текста скрипта ***********************************
   
   FilePath := A_Temp "\_MyScript_" A_TickCount ".ahk"
   FileAppend, % Text%n%, % FilePath
   Run, % FilePath,,, PID
   DetectHiddenWindows, On
   WinWait, % "ahk_pid" PID  ; ожидаем появления главного окна скрипта
   FileDelete, % FilePath
   this[n] := {PID: PID}  ; сохраняем PID дочернего скрипта в соответствующем ключе вызывающего объекта
}

TerminateScript(this, n)
{
   Process, Close, % this[n].PID
}

SendStringToScript(this, n)
{
   GuiControlGet, Text,, Edit1
   SendMessage, WM_SETTEXT := 0xC,, &Text,, % "ahk_pid " this[n].PID
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

9

Re: AHK: Эмуляция множественных потоков

teadrinker, спасибо за красивый и полезный код!
Но порожденный процесс тоже ведь может передать информацию в процессе работы или вернуть код возврата в родительский.
Вопрос - для реализации двухстороннего обмена нужно передавать в "дочку" PID родителя, или обмен можно сделать по-другому?

10

Re: AHK: Эмуляция множественных потоков

А по-моему, без разницы . Можно передать текстом, можно узнать из дочернего скрипта PID родительского:

ScriptManage := {}                       ; создаём объект для управления скриптами
ScriptManage.Run := Func("RunScript")    ; создаём несколько методов
ScriptManage.Terminate := Func("TerminateScript")
ScriptManage.Send := Func("SendStringToScript")
OnExit, OnExit

Gui, +LastFound
Gui, Add, Edit, x10 vEdit, Это строчка, которая будет передана дочернему скрипту.
Gui, Add, Button, x+-79 y+10 w80 gButton, Send
Gui, Add, Button, gButton x10 yp wp Default, Run
GuiControl, Focus, Run
Gui, Show, % "y" A_ScreenHeight//3, % "Мой PID: " DllCall("GetCurrentProcessId")
return

GuiClose:
OnExit:
   for k, v in ScriptManage
      if k is integer
         ScriptManage.Terminate(k)
   ExitApp

Button:
   if (A_GuiControl = "Run")
      ScriptManage.Run(1)   ; единица означает, что запускаем первый скрипт
                            ; у нас он один, но может быть сколько угодно
   if (A_GuiControl = "Terminate")
      ScriptManage.Terminate(1)
   
   if (A_GuiControl = "Send")
      ScriptManage.Send(1)
   
   if (A_GuiControl != "Send")
      GuiControl,, % A_GuiControl, % A_GuiControl = "Run" ? "Terminate" : "Run"
   return

RunScript(this, n)   ; здесь this — вызывающий объект, n — номер скрипта, который будем запускать
{
   ;*********************** это текст скрипта, который будем запускать ************************
   Text1 =  
   (
      #NoTrayIcon 
      
      ParentID := ComObjGet("winmgmts:").Get("Win32_Process.Handle=" DllCall("GetCurrentProcessId")).ParentProcessId
      
      Gui, +AlwaysOnTop +ToolWindow
      Gui, Color, Gray
      Gui, Add, Progress, hwndhControl Border Background606060 x20 y20 w350 h50  
      Gui, Show, `% "NA w390 h90 y" A_ScreenHeight//3 + 120, Мой родительский процесс: `%ParentID`%

      Gui, 2:+Parent`%hControl`% -Caption +Lastfound
      Gui, 2:Color, 606060
      Gui, 2:Font, s21 cFFAA00, Verdana
      Gui, 2:Add, Text, x350 y6, Можно отправить сюда текст из главного скрипта!
      Gui, 2:Show, NA x0 y0
      WinGetPos,,, Width 

      OnMessage(0xC, "WM_SETTEXT")
      Loop
      { 
          Gui, 2:Show, `% "NA y0 x" i := i < -Width ? 0 : i - 1 
          Sleep 10
      }
      return

      GuiClose:
         ExitApp

      WM_SETTEXT(wp, lp)
      {
         global
         Text := StrGet(lp)
         Gui, 2:New, +Parent`%hControl`% -Caption +Lastfound
         Gui, 2:Color, 606060
         Gui, 2:Font, s21 cFFAA00, Verdana
         Gui, 2:Add, Text, x350 y6, `% Text
         Gui, 2:Show, NA x0 y0
         i := 0
         WinGetPos,,, Width
         return 0
      }
   )
   ;******************************** ; конец текста скрипта ***********************************
   
   FilePath := A_Temp "\_MyScript_" A_TickCount ".ahk"
   FileAppend, % Text%n%, % FilePath
   Run, % FilePath,,, PID
   DetectHiddenWindows, On
   WinWait, % "ahk_pid" PID
   FileDelete, % FilePath
   this[n] := {PID: PID}  ; сохраняем PID дочернего скрипта в соответствующем ключе вызывающего объекта
}

TerminateScript(this, n)
{
   Process, Close, % this[n].PID
}

SendStringToScript(this, n)
{
   GuiControlGet, Text,, Edit1
   SendMessage, WM_SETTEXT := 0xC,, &Text,, % "ahk_pid " this[n].PID
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

11

Re: AHK: Эмуляция множественных потоков

Irbis пишет:

Вопрос - для реализации двухстороннего обмена нужно передавать в "дочку" PID родителя, или обмен можно сделать по-другому?

Но быстрее всего, конечно, передавать родительский PID в текст скрипта на этапе формирования через переменную.

Выяснилось, что "хакерский" способ передачи текста через WM_SETTEXT работает не совсем надёжно, т. к. это сообщение иногда проскакивает мимо OnMessage() и в таком случае обрабатывается окном, что приводит к смене заголовка на присланный текст. Поэтому посылать строку текста лучше по старинке через WM_COPYDATA, как показано здесь. Если же нужно послать, например, максимум 2 числа в пределах 4Б (на 64-битных интерпретаторах — 8Б) каждое, можно воспользоваться wParam и lParam своего собственного сообщения, зарегистрированного через RegisterWindowMessage(). В каждом из параметров можно послать также и сразу несколько чисел меньшего размера, используя для записи и чтения поразрядный сдвиг.

В примере ниже родительский скрипт посылает строку текста дочернему через WM_COPYDATA. Дочерний скрипт перед завершением посылает родительскому зарегистрированное сообщение WM_INFO, где в wParam единица, в lParam — PID.

ScriptManage := {}                           ; создаём объект для управления скриптами
ScriptManage.GetScript := Func("GetScript")  ; создаём несколько методов
ScriptManage.Run := Func("RunScript")    
ScriptManage.Terminate := Func("TerminateScript")
ScriptManage.Send := Func("SendStringToScript")

Gui, +LastFound +AlwaysOnTop
Gui, Add, Edit, x10 y12 vEdit, Эту строчку можно передать дочернему скрипту кнопкой Send.
Gui, Add, Button, x+-79 y+10 w80 gButton, Send
Gui, Add, Button, gButton x10 yp wp Default, Run
GuiControl, Focus, Run
Gui, Show, % "y" A_ScreenHeight//3, % "Мой PID: " ScriptManage.ParentID := DllCall("GetCurrentProcessId")

OnMessage(DllCall("RegisterWindowMessage", Str, "WM_INFO"), "WM_INFO_RECEIVER")  ; регистрируем сообщение
OnExit, OnExit               ; которое будем использовать для принятия информации от дочернего скрипта

Loop
{
   WinSetTitle,,, % "Мой PID: " ScriptManage.ParentID " Время: " A_Hour ":" A_Min ":" A_Sec ":" A_MSec//100
   Sleep, 50
}
return

GuiClose:
   ExitApp
   
OnExit:
   for k in ScriptManage
      if k is integer
         ScriptManage.Terminate(k)
   ExitApp

Button:   
   if (A_GuiControl = "Run")
   {
      ScriptManage.Run(1)   ; единица означает, что запускаем первый скрипт
                            ; у нас он один, но может быть сколько угодно
      GuiControl,, Edit1, Эту строчку можно передать дочернему скрипту кнопкой Send.
      GuiControl,, % A_GuiControl, Terminate
   }
   else if (A_GuiControl = "Terminate")
      ScriptManage.Terminate(1)
   else
      ScriptManage.Send(1)
   return

RunScript(this, n)   ; здесь this — вызывающий объект, n — номер скрипта, который будем запускать
{
   FilePath := A_Temp "\_MyScript_" A_TickCount ".ahk"
   FileAppend, % this.GetScript(n), % FilePath
   Run, % FilePath,,, PID
   DetectHiddenWindows, On
   WinWait, % "ahk_pid" PID
   FileDelete, % FilePath
   this[n] := {PID: PID}  ; сохраняем PID дочернего скрипта в соответствующем ключе вызывающего объекта
}

TerminateScript(this, n)
{
   if !this[n].PID
      return
   WinClose, % "ahk_pid" this[n].PID
   this[n].PID := ""
}

SendStringToScript(this, n)
{
   DetectHiddenWindows, On
   if (this[n].PID = "")
   {
      MsgBox, 0x2000,, Запустите дочерний скрипт кнопкой Run!
      return
   }
   
   GuiControlGet, Edit1
   VarSetCapacity(COPYDATASTRUCT, A_PtrSize*3)
   DataSize := (StrLen(Edit1) + 1)*(A_IsUnicode ? 2 : 1)
   NumPut(DataSize, COPYDATASTRUCT, A_PtrSize, "UInt")
   NumPut(&Edit1, COPYDATASTRUCT, A_PtrSize*2)
   
   Loop % Fail := 10
      SendMessage, WM_COPYDATA := 0x4A,, &COPYDATASTRUCT,, % "ahk_pid" this[n].PID
   until ErrorLevel = 1 && !Fail := ""
   
   if Fail
      MsgBox, 0x1010, Ошибка передачи данных, Передача текста не удалась!
}

WM_INFO_RECEIVER(wp, lp)
{
   global ScriptManage
   Command := wp, PID := lp
   if (Command = 1)
   {
      for k,v in ScriptManage
         if (v.PID = PID)
            v.PID := ""
         
      GuiControl,, Edit1, Дочерний процесс PID %PID% завершён!
      GuiControl,, Terminate, Run
   }
}
   
GetScript(this, n)
{
   ParentID := this.ParentID
   Script1 =
   (
      #NoTrayIcon
      SetBatchLines, -1
      PID := DllCall("GetCurrentProcessId"), ParentID := %ParentID%
      OnExit, OnExit
      
      Gui, +AlwaysOnTop +Owner +LastFound
      Gui, Color, Gray
      Gui, Add, Progress, hwndhControl Border Background606060 x20 y20 w350 h50  
      Gui, Show, `% "NA w390 h90 Hide", Мой PID: `%PID`%``, мой ParentID: `%ParentID`%
      WinGetPos, Xp, Yp, Wp,, `% "ahk_pid" ParentID
      WinGetPos, X,, W
      WinMove,,, Xp + (Wp - W)//2, Yp + 120
      WinShow

      Gui, 2:+Parent`%hControl`% -Caption +Lastfound
      Gui, 2:Color, 606060
      Gui, 2:Font, s20 cFFAA00, Verdana
      Gui, 2:Add, Text, x350 y6, Можно отправить сюда текст из главного скрипта!
      Gui, 2:Show, `% "NA y0 x" i := 0
      WinGetPos,,, Width 

      OnMessage(0x4A, "WM_COPYDATA_READ")
      Loop
      { 
          Gui, 2:Show, `% "NA y0 x" -mod(++i, Width)
          Sleep 10
      }
      return

      GuiClose:
         ExitApp
         
      OnExit:
         PostMessage, DllCall("RegisterWindowMessage", Str, "WM_INFO"), 1, PID,, `% "ahk_pid" ParentID
         ExitApp

      WM_COPYDATA_READ(wp, lp)
      {
         global
         Text := StrGet(NumGet(lp + A_PtrSize*2))
         Gui, 2:New, +Parent`%hControl`% -Caption +Lastfound
         Gui, 2:Color, 606060
         Gui, 2:Font, s20 cFFAA00, Verdana
         Gui, 2:Add, Text, x350 y6, `% Text
         Gui, 2:Show, `% "NA y0 x" i := 0
         WinGetPos,,, Width
         return 1
      }
   )
   return Script%n%
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder