Asynchronicznie czekaj na dane wyjściowe z procesu komendy


12

Przede wszystkim wyłączenie odpowiedzialności. Badałem to wiele razy i jestem całkiem pewien, że już znalazłem odpowiedź w ten czy inny sposób, ale po prostu jej nie rozumiem.

Mój problem jest następujący:

  • Mam proces przebiegający przez komendę
  • Chcę wysłać wiersz danych wejściowych, przechwycić dane wyjściowe i zobaczyć, kiedy się skończy (kiedy ostatni wiersz danych wyjściowych pasuje do wyrażenia regularnego w odpowiedzi na monit)
  • dopiero gdy proces zakończy wysyłanie danych wyjściowych, chcę wysłać kolejną linię danych wejściowych (na przykład).

Dla odrobiny tła, pomyśl o głównym trybie implementującym interakcję z programem, który może zwrócić dowolną ilość danych wyjściowych w dowolnie długim czasie. To nie powinna być niezwykła sytuacja, prawda? Okej, może ta część, w której muszę czekać między danymi wejściowymi, jest niezwykła, ale ma kilka zalet w porównaniu z wysyłaniem danych wejściowych jako całości:

  • bufor wyjściowy jest ładnie sformatowany: dane wejściowe dane wyjściowe dane wyjściowe ...
  • co ważniejsze, podczas wysyłania dużej ilości tekstu do procesu, tekst jest cięty na kawałki i kawałki są wklejane z powrotem przez proces; punkty cięcia są dowolne, co czasami powoduje nieprawidłowe wprowadzanie danych (na przykład mój proces nie wkleja poprawnie wycięcia wejściowego w środku identyfikatora)

W każdym razie, niezwykłe czy nie, okazuje się, że jest skomplikowane. W tej chwili używam czegoś w stylu

(defun mymode--wait-for-output ()
  (let ((buffer (mymode-get-buffer)))
    (with-current-buffer buffer
      (goto-char (point-max))
      (forward-line 0)
      (while (not (mymode-looking-at-prompt))
        (accept-process-output nil 0.001)
        (redisplay)
        (goto-char (point-max))
        (forward-line 0))
      (end-of-line))))

i wywołuję to za każdym razem po wysłaniu linii wejściowej, a przed wysłaniem następnej. Cóż ... to działa, to już coś.

Ale powoduje również zawieszenie się emacsa podczas oczekiwania na wynik. Powód jest oczywisty i doszedłem do wniosku, że jeśli włączyłem jakiś rodzaj asynchroniczny sleep-for(na przykład 1s) do pętli, opóźniłoby to wyjście o 1s, ale tłumiłoby zawieszanie. Tyle że wydaje się, że tego rodzaju asynchroniczny sleep-for nie istnieje .

A może to? Mówiąc bardziej ogólnie, czy istnieje idiomatyczny sposób na osiągnięcie tego za pomocą emacsa? Innymi słowy:

Jak wysłać dane wejściowe do procesu, poczekać na dane wyjściowe, a następnie wysłać asynchronicznie więcej danych wejściowych?

Podczas przeszukiwania (patrz powiązane pytania) widziałem głównie wzmianki o wartownikach (ale nie sądzę, żeby miało to zastosowanie w moim przypadku, ponieważ proces się nie kończy) i niektórych haczyków (ale co z tego? ustaw bufor bufora na lokalny, zamień moją „ocenę pozostałych linii” na funkcję, dodaj tę funkcję do haka i wyczyść hak później? to brzmi naprawdę brudno, prawda?).

Przykro mi, jeśli nie wyrażam się jasno lub jeśli gdzieś istnieje naprawdę oczywista odpowiedź, jestem naprawdę zdezorientowany wszystkimi zawiłościami interakcji procesu.

W razie potrzeby mogę uczynić z tego cały działający przykład, ale obawiam się, że stworzy to jeszcze jedno „konkretne pytanie procesowe z konkretną odpowiedzią procesową”, jak wszystkie te, które znalazłem wcześniej i nie pomogło mi.

Niektóre powiązane pytania dotyczące SO:


@nicael Co jest złego w powiązanych linkach?
T. Verron,

Ale dlaczego musisz je uwzględnić?
nicael

2
Uważam, że są to pytania powiązane, nawet jeśli odpowiedzi mi nie pomogły. Jeśli ktoś chce mi pomóc, prawdopodobnie będzie miał głębszą wiedzę na ten temat niż ja, ale być może nadal będzie musiał przeprowadzić badanie wcześniej. W takim przypadku pytania stanowią dla nich punkt wyjścia. I dodatkowo, jeśli któregoś dnia ktoś wyląduje na tej stronie, ale z problemem podobnym do tego, który podlinkowałem, będzie miał skrót do odpowiedniego pytania.
T. Verron,

@nicael (Przepraszam, zapomniałem pingować w pierwszym poście) Czy to problem, że linki nie pochodzą z mx.sx?
T. Verron

Dobrze. Możesz przywrócić swoją wersję, swój post.
nicael

Odpowiedzi:


19

Przede wszystkim nie powinieneś używać, accept-process-outputjeśli chcesz przetwarzać asynchronicznie. Emacs zaakceptuje dane wyjściowe za każdym razem, gdy czeka na dane wejściowe użytkownika.

Właściwym sposobem jest użycie funkcji filtru do przechwycenia wyniku. Nie musisz tworzyć ani usuwać filtrów w zależności od tego, czy nadal masz linie do wysłania. Zamiast tego zwykle deklarujesz pojedynczy filtr na cały czas trwania procesu i używasz zmiennych lokalnych bufora do śledzenia stanu i robienia różnych rzeczy w razie potrzeby.

Interfejs niskiego poziomu

Funkcje filtrów są tym, czego szukasz. Funkcje filtrowania mają wyprowadzać sygnały, które mają zostać zakończone.

(defun mymode--output-filter (process string)
  (let ((buffer (process-buffer process)))
    (when (buffer-live-p buffer)
      (with-current-buffer buffer
        (goto-char (point-max))
        (forward-line 0)
        (when (mymode-looking-at-prompt)
          (do-something)
          (goto-char (point-max)))))))

Spójrz na ręczne lub na wielu przykładach, które pochodzą z Emacs ( grepdla process-filterw .elplikach).

Zarejestruj swoją funkcję filtrowania za pomocą

(set-process-filter 'mymode--output-filter)

Interfejs komend

Comint definiuje funkcję filtrującą, która wykonuje kilka czynności:

  • Przejdź do bufora, który powinien zawierać dane wyjściowe procesu.
  • Uruchom funkcje z listy comint-preoutput-filter-functions, przekazując im nowy tekst jako argument.
  • Dokonaj szybkiej eliminacji duplikatów ad hoc na podstawie comint-prompt-regexp.
  • Wstaw wynik procesu na końcu bufora
  • Uruchom funkcje z listy comint-output-filter-functions, przekazując im nowy tekst jako argument.

Biorąc pod uwagę, że twój tryb oparty jest na komendzie, powinieneś zarejestrować swój filtr w comint-output-filter-functions. Powinieneś ustawić, comint-prompt-regexpaby dopasować monit. Nie sądzę, aby Comint miał wbudowane narzędzie do wykrywania pełnego fragmentu wyjściowego (tj. Między dwoma monitami), ale może to pomóc. Znacznik comint-last-input-endjest ustawiony na końcu ostatniej porcji wejściowej. Nowa porcja danych wyjściowych jest dostępna po zakończeniu ostatniego monitu comint-last-input-end. Jak znaleźć koniec ostatniego monitu, zależy od wersji Emacsa:

  • Do 24,3 nakładka comint-last-prompt-overlayobejmuje ostatni monit.
  • Od 24.4 zmienna comint-last-promptzawiera znaczniki na początku i na końcu ostatniego monitu.
(defun mymode--comint-output-filter (string)
  (let ((start (marker-position comint-last-input-end))
        (end (if (boundp 'comint-last-prompt-overlay)
                 (and comint-last-prompt-overlay (overlay-start comint-last-prompt-overlay))
               (and comint-last-prompt (cdr comint-last-prompt))))
  (when (and start end (< start end))
    (let ((new-output-chunk (buffer-substring-no-properties start end)))
      ...)))

Możesz chcieć dodać zabezpieczenia na wypadek, gdyby proces emitował dane wyjściowe w sekwencji innej niż {odbierać dane wejściowe, emitować dane wyjściowe, wyświetlać monit}.


Zmienna nakładka ostatnia komenda nie wydaje się być zdefiniowana w Emacsie 25 w comint.el. Czy to skądś indziej?
John Kitchin

@JohnKitchin Ta część komendy zmieniła się w 24.4, napisałem swoją odpowiedź dla 24.3. Dodałem metodę po 24.4.
Gilles „SO- przestań być zły”
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.