diff --git a/woman/qt_util.py b/woman/qt_util.py
index 6a4440d04b85509f6093d66bd792c8938744eb36..266da1fea7e2aa1ff41a7fd5b50e4a663b6cbf8a 100644
--- a/woman/qt_util.py
+++ b/woman/qt_util.py
@@ -3,6 +3,7 @@ from PyQt5.QtGui import *
 from PyQt5.QtWidgets import *
 
 from dataclasses import dataclass
+from enum import Enum
 import re
 import types
 import traceback
@@ -361,6 +362,13 @@ class QLabelOpeningBracket(QLabel):
 
 _qt_jobs = set()
 
+class QtJobState(Enum):
+    WAITING = 1
+    RUNNING = 2
+    DONE = 3
+    CANCELED = 4
+
+
 class QtJob:
     def __init__(self, f):
         _qt_jobs.add(self)
@@ -369,21 +377,30 @@ class QtJob:
         self.timer.setSingleShot(True)
         self.timer.timeout.connect(self._run)
         self.timer.start(0)
+        self.state = QtJobState.WAITING
 
     def _clean(self):
         _qt_jobs.remove(self)
 
     def _run(self):
-        try:
-            r = self.f.send(None)
-            print(r)
-        except (StopIteration, GeneratorExit):
-            self._clean()
-        except Exception:
-            traceback.print_exc()
-            self._clean()
-        else:
-            self.timer.start(r)
+        if self.state == QtJobState.WAITING:
+            try:
+                r = self.f.send(None)
+                print(r)
+            except (StopIteration, GeneratorExit):
+                self._clean()
+            except Exception:
+                traceback.print_exc()
+                self._clean()
+            else:
+                self.timer.start(r)
+
+    def cancel(self):
+        if self.state == QtJobState.RUNNING:
+            raise NotImplementedError()
+        if self.state == QtJobState.WAITING:
+            self.state = QtJobState.CANCELED
+            self.f.close()
 
 @types.coroutine
 def qt_job_wait_ms(time_ms):