Vim で簡単に非同期処理を行うラッパを書いた
バックエンドに vimproc を使用して、Vim script で簡単に外部コマンドの非同期処理が行えるようなラッパを書いてみました。
これで比較的簡単に非同期処理を行うことが出来ると思います。
例によって updatetime に依存しているので、その値が大きいと時間は不適格かも知れません。
[使い方]
function! s:echo(str) echo a:str endfunction function! s:async() " 2秒後に "mami" を出力する call g:thread("ruby -e \" sleep 2; puts 'mami' \"", function("s:echo")) endfunction call s:async()
g:thread にコマンドと終了時に呼ばれる関数を指定していして使用します。
上記の場合は、:source した2秒後に "mami" を表示します。
また、複数同時に使用する事も出来ます。
function! s:echo(str) echo a:str endfunction function! s:multi_thread() echo "=== start ===" call g:thread("ruby -e \" sleep 2; puts 'mami' \"", function("s:echo")) call g:thread("ruby -e \" sleep 3; puts 'mado' \"", function("s:echo")) call g:thread("ruby -e \" sleep 1; puts 'homu' \"", function("s:echo")) " 全てのスレッドが終了するまで待つ call g:join_all() echo "=== finish ===" endfunction call s:multi_thread() " output: " === start === " homu " " mami " " mado " " === finish ===
上記の場合は、最後に待ち処理が入るので、:source が終了するまで時間がかかります。
応用すれば、sleep sort なども行うことが出来ます。
function! s:echo(str) echo a:str endfunction function! s:sleep_sort() let data = [3, 6, 2, 8, 1, 9, 7, 4, 5] for n in data let thread = g:thread(printf("ruby -e 'sleep %d; puts %d'", n*2, n), function("s:echo")) endfor " 全てのスレッドの終了待ち call g:join_all() echo "=== finish ===" " 出力を確認する為 sleep 3 endfunction call s:sleep_sort()
若干ばらつきはがありますが、一応は問題なく sort されて出力されていますね。
こんな感じで簡単に非同期で外部コマンドを使用することができます。
今はエラーとか全く考慮していないのでそこら辺をもうちょっとどうにかしたい。
しかし、thread という名前がいまいち微妙。
[example]
function! s:echo(str) echo a:str endfunction function! s:async() " 2秒後に "mami" を出力する call g:thread("ruby -e \" sleep 2; puts 'mami' \"", function("s:echo")) endfunction function! s:multi_thread() echo "=== start ===" call g:thread("ruby -e \" sleep 2; puts 'mami' \"", function("s:echo")) call g:thread("ruby -e \" sleep 3; puts 'mado' \"", function("s:echo")) call g:thread("ruby -e \" sleep 1; puts 'homu' \"", function("s:echo")) " 全てのスレッドが終了するまで待つ call g:join_all() echo "=== finish ===" endfunction function! s:join() echo "=== start ===" let thread = g:thread("ls", function("s:echo")) " 終了待ち call thread.join() echo "=== finish ===" endfunction function! s:thread_group() call g:thread("ruby -e \" sleep 4; puts 'mami' \"", function("s:echo")) let data = [3, 6, 2, 8, 1, 9, 7, 4, 5] let s:result = [] let group = g:thread_group() for n in data let thread = group.create_thread(printf("ruby -e 'sleep %d; puts %d'", n, n)) function! thread.apply(result) let s:result += [ a:result ] endfunction endfor " グループスレッドの終了待ち call group.join_all() echo s:result echo "=== finish ===" endfunction function! s:sleep_sort() let data = [3, 6, 2, 8, 1, 9, 7, 4, 5] for n in data let thread = g:thread(printf("ruby -e 'sleep %d; puts %d'", n*2, n), function("s:echo")) endfor " 全てのスレッドの終了待ち call g:join_all() echo "=== finish ===" " 出力を確認する為 sleep 3 endfunction function! s:apply() echo "=== start ===" let thread = g:thread("ls", function("s:echo")) function! thread.apply(result) echo "result" echo a:result endfunction " 終了待ち call thread.join() echo "=== finish ===" endfunction function! s:ruby() call g:thread("ruby test.rb", function("s:echo")) echo "=== finish ===" endfunction
[実装]
set updatetime=100 function! s:release(threads) for thread in a:threads call thread.release() endfor endfunction if has_key(g:, "thread_list") call s:release(values(g:thread_list)) endif let g:thread_list = {} let g:thread_counter = 0 augroup vim-thread autocmd! autocmd! CursorHold,CursorHoldI * call s:update(values(g:thread_list)) augroup END function! s:update(threads) for thread in a:threads if !thread.is_finish call thread.update() endif endfor endfunction function! s:join(threads) " echo len(filter(copy(a:threads), "!v:val.is_finish")) while len(filter(copy(a:threads), "!v:val.is_finish")) call s:update(a:threads) endwhile endfunction function! s:thread_update(thread) let thread = a:thread let vimproc = thread.vimproc try if !vimproc.stdout.eof let thread.result .= vimproc.stdout.read() endif if !vimproc.stderr.eof let thread.result .= vimproc.stderr.read() endif if !(vimproc.stdout.eof && vimproc.stderr.eof) return 0 endif catch echom v:throwpoint endtry call thread.finish() endfunction function! s:thread_entry(thread) let g:thread_list[g:thread_counter] = a:thread let a:thread.id = g:thread_counter let g:thread_counter += 1 endfunction function! s:thread_release(thread) unlet g:thread_list[a:thread.id] endfunction function! g:join_all() call s:join(values(g:thread_list)) endfunction function! g:make_thread(cmd, ...) let self = { \ "id" : -1, \ "is_finish" : 0, \ "result" : "", \ "command" : a:cmd, \ } if a:0 let self.apply = a:1 endif function! self.update() call s:thread_update(self) endfunction function! self.run() call s:thread_entry(self) let vimproc = vimproc#pgroup_open(self.command) call vimproc.stdin.close() let self.vimproc = vimproc endfunction function! self.join() while !self.is_finish call self.update() endwhile endfunction function! self.release() call s:thread_release(self) if !has_key(self, "vimproc") return endif call self.vimproc.stdout.close() call self.vimproc.stderr.close() call self.vimproc.waitpid() endfunction function! self.finish() let self.is_finish = 1 try if has_key(self, "apply") call self.apply(self.result) endif finally call self.release() endtry endfunction return self endfunction function! g:thread(cmd, ...) let thread = call("g:make_thread", [a:cmd] + a:000) call thread.run() return thread endfunction function! g:thread_group() let self ={ \ "thread_list" : [] \ } function! self.create_thread(...) let thread = call("g:thread", a:000) call add(self.thread_list, thread) return thread endfunction function! self.join_all() call s:join(self.thread_list) endfunction return self endfunction