[{"categories":["tutorials"],"collections":null,"content":"Recently, the topic of AI agents has been very hot. I\u0026rsquo;ve been learning about it too and made a summary of several agents I\u0026rsquo;ve used. ","date":"2026-01-14","heading":"","objectID":"/tutorials/some-ai-agents-guide/:0:0","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#"},{"categories":["tutorials"],"collections":null,"content":"Models The most popular models across the internet are still those from Anthropic, Google, and OpenAI. However, since their servers are overseas, it\u0026rsquo;s a bit troublesome to use them in China. Additionally, overseas models are quite expensive. So I\u0026rsquo;ve been using the GLM Coding Plan, which is much more affordable. ","date":"2026-01-14","heading":"Models","objectID":"/tutorials/some-ai-agents-guide/:1:0","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#models"},{"categories":["tutorials"],"collections":null,"content":"Claude Code First, let me introduce the currently most popular Claude Code, hereinafter referred to as CC. Although CC is a tool specifically designed for Claude models, it can also be configured to use third-party models, and the effect is pretty good. ","date":"2026-01-14","heading":"Claude Code","objectID":"/tutorials/some-ai-agents-guide/:2:0","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#claude-code"},{"categories":["tutorials"],"collections":null,"content":"Installation irm https://claude.ai/install.ps1 | iex After installation, you can open CC with the claude command. If you have a Claude Pro account, you can start using Claude now. Of course, here I want to use third-party models, so additional configuration is needed. ","date":"2026-01-14","heading":"Installation","objectID":"/tutorials/some-ai-agents-guide/:2:1","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#installation"},{"categories":["tutorials"],"collections":null,"content":"Configuration The simplest way to configure is to use Zhipu Qingyan\u0026rsquo;s automated configuration assistant. Run the following command and follow the prompts. npx @z_ai/coding-helper Alternatively, if you want to configure manually, edit the $HOME/.claude/settings.json configuration file, copy the following configuration, and fill in your own API KEY. If this file doesn\u0026rsquo;t exist, you need to create it manually. { \"env\": { \"ANTHROPIC_AUTH_TOKEN\": \"your-api-key\", \"ANTHROPIC_BASE_URL\": \"https://open.bigmodel.cn/api/anthropic\", \"API_TIMEOUT_MS\": \"3000000\", \"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC\": \"1\" } } After successful configuration, switch to your project in the command line, open CC, and there should be no login prompt, which means the configuration is successful. It\u0026rsquo;s worth noting that after enabling Zhipu Qingyan\u0026rsquo;s model, although the model displayed in the CC interface is still the claude model, it has actually been replaced with the GLM model. This point needs attention. ","date":"2026-01-14","heading":"Configuration","objectID":"/tutorials/some-ai-agents-guide/:2:2","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#configuration"},{"categories":["tutorials"],"collections":null,"content":"Configuring Skills Recently, Claude also launched a powerful feature - Agent Skills. Essentially, it\u0026rsquo;s still a prompt, telling the large model what to do now. But this is where it\u0026rsquo;s interesting. Previously, to make large models implement complex functions, we needed to configure agents one by one, then design flowcharts to connect them. This was not only troublesome but also not universal, and it consumed quite a lot of tokens. Claude Skills are different. They are actually some Markdown format files and code, which contain the skills and instructions that large models need to use. Large models will load these files when needed. The benefit of this approach is not only saving tokens, but more importantly, it separates the skills needed by large models individually, achieving universalization. In fact, there is now a Claude Skills Market with a large number of community skills. You can install them like plugins to enhance the capabilities of large models. It\u0026rsquo;s very simple to configure skills in CC, because this concept was proposed by them, so you can directly use the built-in functions to install skills. Taking the planning-with-files skill as an example, open CC and enter the following two commands in order to install this skill. /plugin marketplace add OthmanAdi/planning-with-files /plugin install planning-with-files@planning-with-files To query more skills, you can visit the following websites. It should be noted that skills may have security risks, so it\u0026rsquo;s best to check them yourself before using. skillsmp mcpservers ","date":"2026-01-14","heading":"Configuring Skills","objectID":"/tutorials/some-ai-agents-guide/:2:3","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#configuring-skills"},{"categories":["tutorials"],"collections":null,"content":"opencode Recently there\u0026rsquo;s also a fairly hot open-source code agent called opencode. ","date":"2026-01-14","heading":"opencode","objectID":"/tutorials/some-ai-agents-guide/:3:0","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#opencode"},{"categories":["tutorials"],"collections":null,"content":"Installation npm i -g opencode-ai opencode\u0026rsquo;s usage is similar to Claude Code. It\u0026rsquo;s worth mentioning that opencode can use the free GLM-4.7 model by default, so if you just want to try it out, opencode is a good choice. ","date":"2026-01-14","heading":"Installation","objectID":"/tutorials/some-ai-agents-guide/:3:1","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#installation-1"},{"categories":["tutorials"],"collections":null,"content":"Factory Droid This is also a very popular agent. ","date":"2026-01-14","heading":"Factory Droid","objectID":"/tutorials/some-ai-agents-guide/:4:0","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#factory-droid"},{"categories":["tutorials"],"collections":null,"content":"Installation irm https://app.factory.ai/cli/windows | iex ","date":"2026-01-14","heading":"Installation","objectID":"/tutorials/some-ai-agents-guide/:4:1","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#installation-2"},{"categories":["tutorials"],"collections":null,"content":"Configuration Automatic configuration. npx @z_ai/coding-helper The configuration file path is $HOME/.factory/config.json, and the file content is as follows. { \"custom_models\": [ { \"model_display_name\": \"GLM-4.7 [GLM Coding Plan China]\", \"model\": \"glm-4.7\", \"base_url\": \"https://open.bigmodel.cn/api/coding/paas/v4\", \"api_key\": \"your_api_key\", \"provider\": \"generic-chat-completion-api\", \"max_tokens\": 131072 } ] } ","date":"2026-01-14","heading":"Configuration","objectID":"/tutorials/some-ai-agents-guide/:4:2","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#configuration-1"},{"categories":["tutorials"],"collections":null,"content":"crush ","date":"2026-01-14","heading":"crush","objectID":"/tutorials/some-ai-agents-guide/:5:0","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#crush"},{"categories":["tutorials"],"collections":null,"content":"Installation npm install -g @charmland/crush ","date":"2026-01-14","heading":"Installation","objectID":"/tutorials/some-ai-agents-guide/:5:1","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#installation-3"},{"categories":["tutorials"],"collections":null,"content":"Configuration Automatic configuration. npx @z_ai/coding-helper The configuration file path is $HOME/.config/crush/crush.json, and the content is as follows. { \"providers\": { \"zai\": { \"id\": \"zai\", \"name\": \"ZAI Provider\", \"base_url\": \"https://open.bigmodel.cn/api/coding/paas/v4\", \"api_key\": \"your-key\" } } } ","date":"2026-01-14","heading":"Configuration","objectID":"/tutorials/some-ai-agents-guide/:5:2","tags":["AI"],"title":"A Tutorial on Using Some AI Agents","uri":"/tutorials/some-ai-agents-guide/#configuration-2"},{"categories":["tutorials"],"collections":null,"content":"I\u0026rsquo;ve tried many browsers over the years, but I keep coming back to Firefox. The deciding features for me were simple: opening bookmarks in a new tab and showing search results in a new tab. I was surprised that mainstream browsers like Chrome and Edge don\u0026rsquo;t provide these small conveniences, so Firefox became the obvious choice. This article documents some of the Firefox settings and tweaks I use. ","date":"2025-11-19","heading":"","objectID":"/tutorials/firefox/:0:0","tags":["software"],"title":"Firefox Browser Guide","uri":"/tutorials/firefox/#"},{"categories":["tutorials"],"collections":null,"content":"Feature Customization ","date":"2025-11-19","heading":"Feature Customization","objectID":"/tutorials/firefox/:1:0","tags":["software"],"title":"Firefox Browser Guide","uri":"/tutorials/firefox/#feature-customization"},{"categories":["tutorials"],"collections":null,"content":"Enable Vertical Tabs Right-click on an empty area of the Firefox toolbar and choose \u0026ldquo;Customize Toolbar\u0026rdquo; — this lets you heavily customize the browser\u0026rsquo;s UI to your taste. Arrange the toolbar and other UI elements however you prefer. I also recommend right-clicking the tab bar and selecting \u0026ldquo;Enable vertical tabs\u0026rdquo; (or using an extension that provides vertical tabs). Modern monitors are wide, and many websites are not well-optimized for wide layouts, leaving large blank margins on both sides. Switching to vertical tabs makes better use of the horizontal space and frees up some vertical space. If you tend to keep many tabs open at once, vertical tabs also make it easier to find the tab you want. ","date":"2025-11-19","heading":"Enable Vertical Tabs","objectID":"/tutorials/firefox/:1:1","tags":["software"],"title":"Firefox Browser Guide","uri":"/tutorials/firefox/#enable-vertical-tabs"},{"categories":["tutorials"],"collections":null,"content":"Open Bookmarks and Search Results in New Tabs This is one of the features I find most useful. Surprisingly, most mainstream browsers don\u0026rsquo;t provide it, but Firefox (and some Chromium forks) can. With these settings enabled, you don\u0026rsquo;t have to create a blank tab first and then open a bookmark or start a search — the result opens directly in a new tab. To enable these options, open about:config in Firefox and search for the following preferences. Set each one to true (or the recommended value) to enable the behavior: browser.search.openintab browser.tabs.loadBookmarksInTabs browser.urlbar.openintab ","date":"2025-11-19","heading":"Open Bookmarks and Search Results in New Tabs","objectID":"/tutorials/firefox/:1:2","tags":["software"],"title":"Firefox Browser Guide","uri":"/tutorials/firefox/#open-bookmarks-and-search-results-in-new-tabs"},{"categories":["tutorials"],"collections":null,"content":"Browser Appearance ","date":"2025-11-19","heading":"Browser Appearance","objectID":"/tutorials/firefox/:2:0","tags":["software"],"title":"Firefox Browser Guide","uri":"/tutorials/firefox/#browser-appearance"},{"categories":["tutorials"],"collections":null,"content":"Firefox-WinUI Theme The default Firefox look is fine, but you can customize appearance using user themes and extensions. I use the Firefox-WinUI user theme (https://github.com/Lockframe/Firefox-WinUI) to integrate Firefox with Windows 11 and enable acrylic (translucent) effects — the result looks great. ","date":"2025-11-19","heading":"Firefox-WinUI Theme","objectID":"/tutorials/firefox/:2:1","tags":["software"],"title":"Firefox Browser Guide","uri":"/tutorials/firefox/#firefox-winui-theme"},{"categories":["tutorials"],"collections":null,"content":"Copy Profile Files Open about:support in Firefox and locate the Profile Folder. Click the \u0026ldquo;Open Folder\u0026rdquo; button next to it to open the profile directory in your file explorer. Download the theme package from the Firefox-WinUI repository, extract it, and copy the chrome folder and the user.js file into your Firefox profile folder. ","date":"2025-11-19","heading":"Copy Profile Files","objectID":"/tutorials/firefox/:2:2","tags":["software"],"title":"Firefox Browser Guide","uri":"/tutorials/firefox/#copy-profile-files"},{"categories":["tutorials"],"collections":null,"content":"Theme Settings Finally, open about:config and add or modify the following preferences. The Firefox-WinUI project contains additional configurable options — refer to its README if you want more customization. Enable the browser\u0026rsquo;s translucent/Mica effects: widget.windows.mica widget.windows.mica.popups (set to 1 or 2) browser.tabs.allow_transparent_browser uc.winui.more-acrylic (adds acrylic effect to the navigation bar, bookmarks bar, and selected tab) uc.winui.acrylic-animations (experimental acrylic animation effects) After applying these settings, your Firefox should get a visual refresh with beautiful translucent effects. Enjoy using Firefox! ","date":"2025-11-19","heading":"Theme Settings","objectID":"/tutorials/firefox/:2:3","tags":["software"],"title":"Firefox Browser Guide","uri":"/tutorials/firefox/#theme-settings"},{"categories":["tutorials"],"collections":null,"content":"One of the most criticized aspects of Windows 11 is its context menu, which splits the original menu functions into a secondary menu, making operations more cumbersome. As a result, many people choose to revert the context menu to the traditional style. However, some users prefer the Windows 11 context menu. So, is it possible to add items directly to the Windows 11 context menu? Of course! Today, I want to introduce ContextMenuForWindows11, a tool that allows you to customize the Windows 11 context menu and set it up just the way you like. ","date":"2025-06-04","heading":"","objectID":"/tutorials/context-menu-for-windows11/:0:0","tags":["windows"],"title":"🛠️ ContextMenuForWindows11","uri":"/tutorials/context-menu-for-windows11/#"},{"categories":["tutorials"],"collections":null,"content":"📥 Installation First, you need to download the software. The easiest way is to install it directly from the Microsoft Store, but the store version is paid. If you don\u0026rsquo;t want to pay, you can install the GitHub version. Before installing, you need to install the certificate: open the certificate file, import ➡️ Local Machine ➡️ Place all certificates in the following store ➡️ Trusted People, then click Finish. Next, install the appx package. If you are prompted that dependencies are missing, just install the required dependencies. ","date":"2025-06-04","heading":"📥 Installation","objectID":"/tutorials/context-menu-for-windows11/:1:0","tags":["windows"],"title":"🛠️ ContextMenuForWindows11","uri":"/tutorials/context-menu-for-windows11/#-installation"},{"categories":["tutorials"],"collections":null,"content":"🖱️ Usage The software is very easy to use. Click the plus sign on the left, then enter the menu item\u0026rsquo;s title, launch path, parameters, icon, etc., and finally click Save at the bottom. This will successfully add a new Windows 11 context menu item, which you can use from the right-click menu in the future! 🖼️ ","date":"2025-06-04","heading":"🖱️ Usage","objectID":"/tutorials/context-menu-for-windows11/:2:0","tags":["windows"],"title":"🛠️ ContextMenuForWindows11","uri":"/tutorials/context-menu-for-windows11/#-usage"},{"categories":["linux"],"collections":null,"content":"When installing Fedora 42 Workstation on a physical machine or virtual machine, you need to enable third-party repositories during the installation process. After installation, you can follow this guide to further configure your system. If you are using a different version of Fedora, some configurations in this article may not apply. ","date":"2025-05-31","heading":"","objectID":"/posts/things-to-do-after-installing-fedora-42/:0:0","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#"},{"categories":["linux"],"collections":null,"content":"System Configuration ","date":"2025-05-31","heading":"System Configuration","objectID":"/posts/things-to-do-after-installing-fedora-42/:1:0","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#system-configuration"},{"categories":["linux"],"collections":null,"content":"No Password sudo By default, running sudo commands requires entering a password. If you don\u0026rsquo;t want to enter your password every time you use sudo, you can run the following command: echo \"$(whoami) ALL=(ALL) NOPASSWD: ALL\" | sudo tee \"/etc/sudoers.d/$(whoami)\" ","date":"2025-05-31","heading":"No Password sudo","objectID":"/posts/things-to-do-after-installing-fedora-42/:1:1","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#no-password-sudo"},{"categories":["linux"],"collections":null,"content":"System Update The system is not up to date by default after installation. It\u0026rsquo;s best to update your system before performing other operations to keep it current. sudo dnf -y update After the update, it\u0026rsquo;s recommended to reboot your system. ","date":"2025-05-31","heading":"System Update","objectID":"/posts/things-to-do-after-installing-fedora-42/:1:2","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#system-update"},{"categories":["linux"],"collections":null,"content":"NVIDIA Driver If you use an NVIDIA graphics card, install the following driver: sudo dnf install -y akmod-nvidia Wait a moment, then run the following command to check if the NVIDIA kernel module has been built. After that, reboot your system. modinfo -F version nvidia ","date":"2025-05-31","heading":"NVIDIA Driver","objectID":"/posts/things-to-do-after-installing-fedora-42/:1:3","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#nvidia-driver"},{"categories":["linux"],"collections":null,"content":"Set Hostname sudo hostnamectl set-hostname myfedora ","date":"2025-05-31","heading":"Set Hostname","objectID":"/posts/things-to-do-after-installing-fedora-42/:1:4","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#set-hostname"},{"categories":["linux"],"collections":null,"content":"Optimize Boot Speed sudo systemctl disable NetworkManager-wait-online.service sudo rm /etc/xdg/autostart/org.gnome.Software.desktop ","date":"2025-05-31","heading":"Optimize Boot Speed","objectID":"/posts/things-to-do-after-installing-fedora-42/:1:5","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#optimize-boot-speed"},{"categories":["linux"],"collections":null,"content":"System Beautification ","date":"2025-05-31","heading":"System Beautification","objectID":"/posts/things-to-do-after-installing-fedora-42/:2:0","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#system-beautification"},{"categories":["linux"],"collections":null,"content":"Install GNOME Shell Extensions First, install the extension and tweak tools: sudo dnf install -y gnome-tweaks gnome-extensions-app Here are some GNOME Shell extensions you can install as needed: sudo dnf install -y gnome-shell-extension-pop-shell xprop sudo dnf install -y gnome-shell-extension-user-theme sudo dnf install -y gnome-shell-extension-just-perfection sudo dnf install -y gnome-shell-extension-dash-to-dock sudo dnf install -y gnome-shell-extension-blur-my-shell sudo dnf install -y gnome-shell-extension-caffeine sudo dnf install -y gnome-shell-extension-drive-menu Some GNOME Shell extensions can only be installed online: vitals: An extension that displays system information in the top bar After installation, you can enable and adjust the settings of each extension in the Extensions app. ","date":"2025-05-31","heading":"Install GNOME Shell Extensions","objectID":"/posts/things-to-do-after-installing-fedora-42/:2:1","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#install-gnome-shell-extensions"},{"categories":["linux"],"collections":null,"content":"Window Effects The following extensions also need to be enabled in the Extensions app: Burn My Windows - GNOME Shell Extensions: Window open/close effects compiz-windows-effect: Window movement effects compiz-alike-magic-lamp-effect: Window maximize/minimize effects ","date":"2025-05-31","heading":"Window Effects","objectID":"/posts/things-to-do-after-installing-fedora-42/:2:2","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#window-effects"},{"categories":["linux"],"collections":null,"content":"Install Icon Theme git clone https://github.com/vinceliuice/Tela-icon-theme --depth=1 cd Tela-icon-theme ./install.sh -a ","date":"2025-05-31","heading":"Install Icon Theme","objectID":"/posts/things-to-do-after-installing-fedora-42/:2:3","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#install-icon-theme"},{"categories":["linux"],"collections":null,"content":"WhiteSur Theme Clone the theme and install it: git clone https://github.com/vinceliuice/WhiteSur-gtk-theme --depth=1 cd WhiteSur-gtk-theme ./install.sh ","date":"2025-05-31","heading":"WhiteSur Theme","objectID":"/posts/things-to-do-after-installing-fedora-42/:2:4","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#whitesur-theme"},{"categories":["linux"],"collections":null,"content":"grub2 Theme git clone https://github.com/vinceliuice/grub2-themes --depth=1 cd grub2-themes sudo ./install.sh -t whitesur -i whitesur ","date":"2025-05-31","heading":"grub2 Theme","objectID":"/posts/things-to-do-after-installing-fedora-42/:2:5","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#grub2-theme"},{"categories":["linux"],"collections":null,"content":"Terminal Configuration Use zsh: sudo dnf install -y zsh curl -fsSL https://raw.githubusercontent.com/zimfw/install/master/install.zsh | zsh Use fish: sudo dnf install -y zsh ","date":"2025-05-31","heading":"Terminal Configuration","objectID":"/posts/things-to-do-after-installing-fedora-42/:2:6","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#terminal-configuration"},{"categories":["linux"],"collections":null,"content":"Reference https://github.com/devangshekhawat/Fedora-42-Post-Install-Guide?tab=readme-ov-file ","date":"2025-05-31","heading":"Reference","objectID":"/posts/things-to-do-after-installing-fedora-42/:3:0","tags":["linux","fedora"],"title":"Things to Do After Installing Fedora 42","uri":"/posts/things-to-do-after-installing-fedora-42/#reference"},{"categories":["python"],"collections":null,"content":"Today, I\u0026rsquo;ll introduce how to use asynchronous programming in Python. This article focuses on the usage of asynchronous programming, so it won\u0026rsquo;t delve too much into underlying concepts. ","date":"2025-05-09","heading":"","objectID":"/posts/understanding-python-async-in-ten-minutes/:0:0","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#"},{"categories":["python"],"collections":null,"content":"Brief Introduction to Concepts Coroutine: A special function marked with async that can execute code asynchronously. Event Loop: The core of Python asynchronous programming, responsible for scheduling coroutines. Future: A low-level object representing the eventual result of an asynchronous operation. Task: A wrapper for Future that can encapsulate coroutines and supports operations like cancellation. We all know that in operating systems, creating and switching processes and threads is very resource-intensive, making it prone to performance bottlenecks under high loads. Coroutines, on the other hand, are smaller execution units defined within the program. Since they don\u0026rsquo;t involve the overhead of process and thread context switching, coroutines are much lighter. We can easily create and run thousands of coroutines in a program—something that would be impossible with processes and threads. Although coroutines may sound magical, they are actually not much different from ordinary functions. The only difference is that Python coroutines run on an event loop, which gives them their magical concurrency capabilities. When we execute a regular function, if it takes 1 second to complete, all subsequent code must wait for the current function to finish before continuing—the entire program execution is a straight line. However, when we execute a coroutine on an event loop, if the current coroutine needs to wait for 1 second, the event loop will pause executing this coroutine and immediately look for the next executable coroutine. After 1 second, it will return to continue executing the paused coroutine. The entire program execution resembles a circular pipeline, looping back and forth—hence the name \u0026ldquo;event loop.\u0026rdquo; It\u0026rsquo;s important to note that an event loop is a form of pseudo-concurrency, not true concurrent execution. If you\u0026rsquo;re performing I/O-intensive tasks, such as network connections or data read/write operations, the CPU is essentially idle, waiting for external data input. During this time, the event loop can use the CPU\u0026rsquo;s idle time to execute the next task. However, if you\u0026rsquo;re performing CPU-intensive tasks, such as file compression, the CPU is fully occupied, leaving no room for the event loop to execute the next task. Therefore, if you need to perform multiple CPU-intensive tasks simultaneously, the only solution is to create multiple processes or threads to leverage the CPU\u0026rsquo;s multi-core capabilities for true concurrency. ","date":"2025-05-09","heading":"Brief Introduction to Concepts","objectID":"/posts/understanding-python-async-in-ten-minutes/:1:0","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#brief-introduction-to-concepts"},{"categories":["python"],"collections":null,"content":"Executing Asynchronous Code There are a few key points to remember when using coroutines: If a function is marked with the async keyword, it becomes a coroutine. Directly calling a coroutine only returns the coroutine object—it doesn\u0026rsquo;t execute the coroutine. To execute the coroutine and get its result, you need to await it. await cannot be used directly in regular functions; it can only be called within coroutines. You might wonder: if we use coroutines in a program, then to call a coroutine, the parent function must also be marked as async. This would propagate upward layer by layer until the main function itself becomes a coroutine. So how do we run the main function? The answer is to use asyncio.run. This function creates an event loop and executes the coroutine within it. In fact, the most common—and correct—use of run is to execute the main coroutine. You might think: can we just use run to call coroutines directly, so that parent functions don\u0026rsquo;t need to be marked as async? While this is technically possible, run itself manages the lifecycle of the event loop. Instead of frequently creating and destroying event loops by calling run multiple times, it\u0026rsquo;s better to place the main coroutine inside it. The run function is designed specifically to execute non-blocking program entry points. import asyncio async def task1(): await asyncio.sleep(2) return 42 async def main(): rst = await task1() print(rst) if __name__ == \"__main__\": asyncio.run(main()) # 42 ","date":"2025-05-09","heading":"Executing Asynchronous Code","objectID":"/posts/understanding-python-async-in-ten-minutes/:2:0","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#executing-asynchronous-code"},{"categories":["python"],"collections":null,"content":"Concurrency ","date":"2025-05-09","heading":"Concurrency","objectID":"/posts/understanding-python-async-in-ten-minutes/:3:0","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#concurrency"},{"categories":["python"],"collections":null,"content":"Concurrent Code Example First, let\u0026rsquo;s look at the following code example. This example demonstrates several ways to run coroutines. When executing this example, you\u0026rsquo;ll notice that the async_sequential_tasks() coroutine takes a total of 2 seconds, the same as the sequentially executed version. Aren\u0026rsquo;t coroutines supposed to run concurrently? What\u0026rsquo;s going on here? import asyncio import time from functools import wraps from loguru import logger def sync_task(f: float): time.sleep(f) return \"foo\" async def async_task(f: float): await asyncio.sleep(f) return \"bar\" def measure_time(func): @wraps(func) def sync_wrapper(*args, **kwargs): start_time = time.perf_counter() result = func(*args, **kwargs) end_time = time.perf_counter() logger.info(f\"{func.__name__} used: {end_time - start_time:.4f} seconds.\") return result @wraps(func) async def async_wrapper(*args, **kwargs): start_time = time.perf_counter() result = await func(*args, **kwargs) end_time = time.perf_counter() logger.info(f\"{func.__name__} used: {end_time - start_time:.4f} seconds.\") return result if asyncio.iscoroutinefunction(func): return async_wrapper else: return sync_wrapper @measure_time def sequential_tasks(): r1 = sync_task(0.5) r2 = sync_task(1.5) logger.info([r1, r2]) @measure_time async def async_sequential_tasks(): r1 = await async_task(0.5) r2 = await async_task(1.5) logger.info([r1, r2]) @measure_time async def parallel_tasks(): r1, r2 = await asyncio.gather(async_task(0.5), async_task(1.5)) logger.info([r1, r2]) @measure_time async def parallel_tasks_using_task_group(): async with asyncio.TaskGroup() as tg: r1 = tg.create_task(async_task(0.5)) r2 = tg.create_task(async_task(1.5)) logger.info([r1.result(), r2.result()]) async def main(): sequential_tasks() await async_sequential_tasks() await parallel_tasks() await parallel_tasks_using_task_group() if __name__ == \"__main__\": asyncio.run(main()) # 23:32:32.565 | INFO | __main__:sequential_tasks:45 - ['foo', 'foo'] # 23:32:32.565 | INFO | __main__:sync_wrapper:24 - sequential_tasks used: 2.0011 seconds. # 23:32:34.568 | INFO | __main__:async_sequential_tasks:52 - ['bar', 'bar'] # 23:32:34.568 | INFO | __main__:async_wrapper:32 - async_sequential_tasks used: 2.0031 seconds. # 23:32:36.069 | INFO | __main__:parallel_tasks:58 - ['bar', 'bar'] # 23:32:36.069 | INFO | __main__:async_wrapper:32 - parallel_tasks used: 1.5007 seconds. # 23:32:37.571 | INFO | __main__:parallel_tasks_using_task_group:66 - ['bar', 'bar'] # 23:32:37.571 | INFO | __main__:async_wrapper:32 - parallel_tasks_using_task_group used: 1.5020 seconds. The reason is actually quite simple: await blocks the execution of the code until the coroutine completes and returns a result. So, if you await two coroutines in the usual way, the effect is the same as executing the two functions sequentially. To make coroutines run concurrently, you need a specific method to run them. ","date":"2025-05-09","heading":"Concurrent Code Example","objectID":"/posts/understanding-python-async-in-ten-minutes/:3:1","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#concurrent-code-example"},{"categories":["python"],"collections":null,"content":"gather asyncio.gather is a function that can run multiple coroutines concurrently and retrieve their return values. If the return_exceptions=True parameter is specified, and one of the coroutines raises an exception, the gather function will not directly throw the exception but will continue running and wrap the exception in the return value list. When you await the gather function, it will only return after all the passed coroutines have completed execution. If you want to execute multiple coroutines simultaneously and don\u0026rsquo;t want an exception in one coroutine to affect the normal execution of others, then choose the gather function. ","date":"2025-05-09","heading":"gather","objectID":"/posts/understanding-python-async-in-ten-minutes/:3:2","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#gather"},{"categories":["python"],"collections":null,"content":"Task Groups Python 3.11 also provides a new way to execute coroutines: task groups, which is the method demonstrated in the code above. Task groups offer a modern approach to managing the execution of multiple tasks. Tasks added to a task group start running immediately without needing to await. The async with block will end once all tasks have completed. You can still submit tasks to the task group before the last task finishes, but once the task group ends, you cannot. At this point, calling the result() function of a task will retrieve its return value. If any task raises an exception, other tasks will be canceled, and the task group will raise an ExceptionGroup or BaseExceptionGroup exception, which wraps the exceptions thrown by the tasks. If you want to execute multiple coroutines simultaneously and have all coroutines stop if any one raises an exception, use a task group. If you need to create a single task, you can directly use the asyncio.create_task function. If you need to create multiple related tasks, using a task group is more appropriate. Why do we need tasks in addition to coroutines? After all, tasks seem to just wrap coroutines. This is indeed necessary because coroutines are just the basic units of execution. For more advanced operations, such as cancellation, tasks are essential. In fact, besides coroutines and tasks, there is also a lower-level object called Future, which is related to asynchronous operations, and tasks actually inherit from Future. However, Future is a low-level object that we don\u0026rsquo;t usually need to use. Coroutines, tasks, and Future are all awaitable objects and can be used with await. ","date":"2025-05-09","heading":"Task Groups","objectID":"/posts/understanding-python-async-in-ten-minutes/:3:3","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#task-groups"},{"categories":["python"],"collections":null,"content":"Blocking IO The asyncio library can only be used to asynchronously execute non-blocking IO operations, specifically those libraries that explicitly support asynchronous operations, such as httpx. Traditional libraries like requests only support synchronous, blocking IO. In such cases, you can use the asyncio.to_thread function to run blocking IO operations in another thread, avoiding blocking the current event loop. Due to the existence of the GIL, to_thread can only handle IO-bound tasks. However, on Python implementations like PyPy that do not have a GIL, this limitation does not apply. The following code uses time.sleep to simulate blocking IO. This function pauses the current thread, so it must be run in another thread via to_thread. You might notice other examples using asyncio.sleep to simulate long-running tasks. asyncio.sleep supports asynchronous operations, so it does not block the event loop. ","date":"2025-05-09","heading":"Blocking IO","objectID":"/posts/understanding-python-async-in-ten-minutes/:3:4","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#blocking-io"},{"categories":["python"],"collections":null,"content":"Exception Handling ","date":"2025-05-09","heading":"Exception Handling","objectID":"/posts/understanding-python-async-in-ten-minutes/:4:0","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#exception-handling"},{"categories":["python"],"collections":null,"content":"Exception Handling Code Example As usual, here we use a piece of code to demonstrate how to handle asynchronous exceptions. Since task cancellation and timeouts also throw exceptions, we might as well cover them here together. import asyncio from random import choice from loguru import logger async def number_task(n: int): wait_seconds = choice([1, 2, 3, 4, 5]) * 0.5 await asyncio.sleep(wait_seconds) if n == 2: raise ValueError(f\"Met invalid value {n}\") return n async def very_long_work(): logger.info(\"very long work started\") await asyncio.sleep(3600) print(\"SO SO SO SO SO LONG\") async def timeout(): try: await asyncio.wait_for(very_long_work(), timeout=2) except TimeoutError: logger.error(\"timeout error: the task took too long\") async def handle_exceptions(): tasks = [number_task(n) for n in range(5)] logger.info(\"gather tasks started\") results = await asyncio.gather(*tasks, return_exceptions=True) numbers = [] exceptions = [] for result in results: if isinstance(result, Exception): exceptions.append(result) else: numbers.append(result) logger.info(numbers) logger.error(exceptions) async def task_group_with_exceptions(): try: logger.info(\"task group started\") async with asyncio.TaskGroup() as tg: for i in range(5): tg.create_task(number_task(i)) except BaseExceptionGroup as e: logger.error(e) logger.error(e.exceptions) async def cancellation(): task = asyncio.create_task(very_long_work()) await asyncio.sleep(1) task.cancel() await asyncio.sleep(1) try: await task except asyncio.CancelledError: logger.info(\"Task was cancelled\") async def main(): await timeout() await cancellation() await handle_exceptions() await task_group_with_exceptions() if __name__ == \"__main__\": asyncio.run(main()) ","date":"2025-05-09","heading":"Exception Handling Code Example","objectID":"/posts/understanding-python-async-in-ten-minutes/:4:1","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#exception-handling-code-example"},{"categories":["python"],"collections":null,"content":"Task Timeout You can use the asyncio.wait_for function to specify a time limit for a coroutine. If the task exceeds this time, it will be automatically canceled and a TimeoutError will be raised. If you only want a timeout notification without canceling the task, wrap the task with the asyncio.shield function, which prevents the task from being canceled. ","date":"2025-05-09","heading":"Task Timeout","objectID":"/posts/understanding-python-async-in-ten-minutes/:4:2","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#task-timeout"},{"categories":["python"],"collections":null,"content":"Canceling Tasks If we encapsulate a coroutine into a task, we can manually cancel the task when needed. Canceling a task will raise a CancelledError. If the task is canceled within a task group, it will not interrupt the execution of other tasks—only exceptions other than CancelledError will halt the task group\u0026rsquo;s execution. If the task group raises an exception, the CancelledError will not be wrapped in an ExceptionGroup. Based on this behavior of task groups, you can choose between gather or task groups to run coroutines concurrently as needed. ","date":"2025-05-09","heading":"Canceling Tasks","objectID":"/posts/understanding-python-async-in-ten-minutes/:4:3","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#canceling-tasks"},{"categories":["python"],"collections":null,"content":"Handling Results and Exceptions Separately If you use the gather function with the return_exceptions=True parameter, the return value will include exceptions. Generally, we prefer to separate normal results from exceptions and handle them individually. ","date":"2025-05-09","heading":"Handling Results and Exceptions Separately","objectID":"/posts/understanding-python-async-in-ten-minutes/:4:4","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#handling-results-and-exceptions-separately"},{"categories":["python"],"collections":null,"content":"Task Group Exceptions When using task groups, you may encounter an ExceptionGroup exception, which contains an exceptions property that allows you to access the sub-exceptions thrown by the tasks and handle them as needed. ","date":"2025-05-09","heading":"Task Group Exceptions","objectID":"/posts/understanding-python-async-in-ten-minutes/:4:5","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#task-group-exceptions"},{"categories":["python"],"collections":null,"content":"Comprehensive Example To conclude, here’s a comprehensive example script that fetches various projects from awesome-dotnet and lists the top 10 projects with the most stars. The script uses the httpx library to send requests, retrieves star count information for hundreds of projects in just a few seconds, and includes features like a progress bar for reference. https://github.com/techstay/study-notes/blob/main/python/thirdparty-libraries/crawler/awesome_dotnet.py ","date":"2025-05-09","heading":"Comprehensive Example","objectID":"/posts/understanding-python-async-in-ten-minutes/:5:0","tags":["python"],"title":"Understanding Python Asynchronous Programming in 10 Minutes","uri":"/posts/understanding-python-async-in-ten-minutes/#comprehensive-example"},{"categories":["java"],"collections":null,"content":"Variance is a concept in programming that is prone to mistakes. I used to have only a vague understanding of it, so this time I specifically looked up a lot of information online to thoroughly study the issue. I also took the opportunity to write a blog post introducing it, so that if I forget in the future, I can quickly recall it. Here, we\u0026rsquo;ll use the classic Java as the primary language to briefly explore this issue. ","date":"2023-05-07","heading":"","objectID":"/posts/understanding-variance-in-10-minutes/:0:0","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#"},{"categories":["java"],"collections":null,"content":"Basic First, let\u0026rsquo;s create some class relationships for demonstration. The example is quite simple: we\u0026rsquo;ll start by creating the classes Animal, Cat, and Dog, which have an inheritance relationship. Then, we\u0026rsquo;ll define a generic class Merchant to buy and sell these types of animals. class Animal { } class Dog extends Animal { } class Cat extends Animal { } class Merchant { public final Deque inventory; public Merchant() { this.inventory = new ArrayDeque\u003c\u003e(); } public Merchant(Collection collection) { this.inventory = new ArrayDeque\u003c\u003e(collection); } public void buy(T t) { inventory.push(t); } public T sell() { return inventory.pop(); } } In this way, we can write the following code: use an animal merchant to buy and sell animals, and use a cat merchant to buy and sell cats. Everything is just so perfect. Merchant animalMerchant = new Merchant\u003c\u003e(); animalMerchant.buy(new Animal()); Animal animal = animalMerchant.sell(); Merchant catMerchant = new Merchant\u003c\u003e(); catMerchant.buy(new Cat()); Cat cat = catMerchant.sell(); Let\u0026rsquo;s think about this: if we now need to buy some animals, but there is only a cat merchant nearby, can we treat this cat merchant as an animal merchant and buy animals from him? Conversely, if we currently have some cats we want to sell to a cat merchant, but there is only an animal merchant, can we treat this animal merchant as a cat merchant and sell the cats to him? These two questions lead us to the main topic of this article: covariance and contravariance. Alright, let\u0026rsquo;s continue reading. ","date":"2023-05-07","heading":"Basic","objectID":"/posts/understanding-variance-in-10-minutes/:1:0","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#basic"},{"categories":["java"],"collections":null,"content":"Covariance and Contravariance ","date":"2023-05-07","heading":"Covariance and Contravariance","objectID":"/posts/understanding-variance-in-10-minutes/:2:0","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#covariance-and-contravariance"},{"categories":["java"],"collections":null,"content":"Covariance First, let\u0026rsquo;s discuss the first question: Can a cat merchant sell animals like an animal merchant? Theoretically, it should be possible, because cats are clearly a type of animal, so a cat merchant can certainly sell cats as animals. However, when we translate this logic into Java code, the compiler throws an error. Why is that? Because the compiler is very strict—even though there is an inheritance relationship between animals and cats, there is no inheritance relationship between animal merchants and cat merchants, so this assignment will fail. // failed Merchant Merchant = new Merchant(List.of(new Cat(), new Cat())); Animal animal = Merchant.sell(); You\u0026rsquo;ve probably all heard of the Liskov Substitution Principle in object-oriented programming, which states that if a type system is designed correctly, a subtype should be able to replace its parent type wherever the parent type appears. However, the Merchant class is a generic class, and only Animal and Cat have an inheritance relationship, which doesn\u0026rsquo;t comply with the Liskov Substitution Principle. Therefore, the compiler doesn\u0026rsquo;t consider the cat merchant and the animal merchant to have a subtype relationship, leading to the error mentioned above. To solve this problem, we need to use covariance. In Java, this requires the somewhat peculiar syntax \u0026lt;? extends Animal\u0026gt;. The question mark represents any type, and with the extends expression, it means the type is uncertain—we only know it\u0026rsquo;s either Animal or any subtype of Animal. This way, our problem is solved. Now, we can treat a cat merchant as an animal merchant and then buy animals from him. // success Merchant\u003c? extends Animal\u003e Merchant = new Merchant(List.of(new Cat(), new Cat())); Animal animal = Merchant.sell(); Originally, there was no subtype relationship between Merchant\u0026lt;Animal\u0026gt; and Merchant\u0026lt;Cat\u0026gt;, but through some manipulation, we established a subtype relationship between them, and this subtype relationship aligns with their generic parameters (Animal and Cat). This situation is called covariance. Let’s look at the second line of code above. An animal merchant can sell animals, but in reality, the animals it sells are more specific (cats). This complies with the Liskov Substitution Principle. ","date":"2023-05-07","heading":"Covariance","objectID":"/posts/understanding-variance-in-10-minutes/:2:1","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#covariance"},{"categories":["java"],"collections":null,"content":"Limitations of Covariance The concept of covariance is quite useful, but it also has some limitations. Take this cat merchant who is treated as an animal merchant. Now, instead of buying from him, we want to sell something to him. However, we find that no matter what we try to sell to this merchant, the compiler reports an error. What’s going on here? Merchant\u003c? extends Animal\u003e Merchant = new Merchant(List.of(new Cat(), new Cat())); Animal animal = Merchant.sell(); // The merchant can buy nothing // Merchant.buy(new Animal()); The reason is actually quite simple: our requirements are somewhat special. When we treat a cat merchant as an animal merchant, although they can freely sell animals (since all their cats are animals), they actually lose the ability to purchase any goods. This is because their current identity is that of an animal merchant, nominally able to buy any animal, but in reality, they can only buy cats. If they attempt to buy other animals like dogs, errors will occur. To prevent such errors, the compiler outright prohibits them from purchasing any animals—meaning even if they want to buy cats, they can\u0026rsquo;t. Put another way, this covariant-supporting \u0026ldquo;animal merchant\u0026rdquo; now only has the ability to sell animals. In other words, they are a producer, capable only of supplying goods to the outside world. ","date":"2023-05-07","heading":"Limitations of Covariance","objectID":"/posts/understanding-variance-in-10-minutes/:2:2","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#limitations-of-covariance"},{"categories":["java"],"collections":null,"content":"Contravariance Now, let’s consider the second question: when we need to sell some cats to a cat merchant, can we use an animal merchant to do so? Theoretically, this should also be possible, since an animal merchant can, of course, accept all kinds of animals. What we have are cats, which should be sellable to an animal merchant. However, if written in Java code, it would still throw an error. Cat catForSale = new Cat(); // failed Merchant catMerchant = new Merchant(); catMerchant.buy(catForSale); With the above experience, we know that the compiler considers these two as different types, so they cannot be arbitrarily substituted. Naturally, there is a solution. We need to use the seemingly even stranger declaration \u0026lt;? super Cat\u0026gt;, which means this generic type can be any type as long as it is a supertype of Cat, the exact opposite of the previous extends. This way, the merchant can safely purchase cats. Cat catForSale = new Cat(); Merchant\u003c? super Cat\u003e catMerchant = new Merchant(); catMerchant.buy(catForSale); Meanwhile, we can also observe an interesting point. Now we\u0026rsquo;re using the animal merchant as a cat merchant to buy cats, meaning there\u0026rsquo;s also a subtype relationship between Merchant\u0026lt;Cat\u0026gt; and Merchant\u0026lt;Animal\u0026gt;, but this subtype relationship is exactly the opposite of their generic parameters (Cat and Animal). Therefore, this situation is called contravariance. Contravariance doesn\u0026rsquo;t actually violate the Liskov Substitution Principle, because in this case, we\u0026rsquo;re having the animal merchant buy cats, which still involves using a subtype (cat) to replace the parent type (the animal merchant only accepts animals). Compared to the covariance mentioned earlier, we\u0026rsquo;ll find that contravariance occurs in function parameters, while covariance occurs in function return values. ","date":"2023-05-07","heading":"Contravariance","objectID":"/posts/understanding-variance-in-10-minutes/:2:3","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#contravariance"},{"categories":["java"],"collections":null,"content":"Limitations of Contravariance Contravariance also comes with some limitations. If we try to buy something from this animal merchant who\u0026rsquo;s being treated as a cat merchant, what would happen? The answer is, we can only get Object. His true identity is an animal merchant, but when he\u0026rsquo;s brought in as a cat merchant, while he gains the ability to buy cats, the trade-off is the \u0026lt;? super Cat\u0026gt; constraint, meaning his goods could be any \u0026ldquo;thing\u0026rdquo; (not just animals), so all we can obtain is just a \u0026ldquo;thing.\u0026rdquo; Cat catForSale = new Cat(); Merchant\u003c? super Cat\u003e catMerchant = new Merchant(); catMerchant.buy(catForSale); // This merchant can only sell things. Object c = catMerchant.sell(); In other words, this cat merchant, whose true identity is an animal merchant, can only buy things—meaning he is a consumer. Although he could technically sell items, the goods he sells are only of type Object. Since we use generics precisely to avoid the Object type, the selling operation is meaningless in this context. After all, we only need him to buy things. ","date":"2023-05-07","heading":"Limitations of Contravariance","objectID":"/posts/understanding-variance-in-10-minutes/:2:4","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#limitations-of-contravariance"},{"categories":["java"],"collections":null,"content":"Invariance Now that we’ve learned some basics about variance, we know that by default, Merchant\u0026lt;Cat\u0026gt; and Merchant\u0026lt;Animal\u0026gt; cannot be used interchangeably—they have no subtype relationship. This scenario also has a term: invariance. The advantage of invariance is safety. An animal merchant can freely buy and sell animals, and a cat merchant can freely buy and sell cats. However, invariance can sometimes be too rigid. If we only need to buy some animals, we can go to a cat merchant, since a cat merchant selling cats still meets our needs. But in this case, the merchant can no longer sell things, as that would break our agreement. This is covariance. Conversely, if we want to sell some cats, we can directly approach an animal merchant, since an animal merchant accepts any animal—including cats—which also aligns with our needs. But then, this merchant can only buy cats and not sell them, because selling would again violate our agreement. This is contravariance. These three concepts together are called variance. ","date":"2023-05-07","heading":"Invariance","objectID":"/posts/understanding-variance-in-10-minutes/:2:5","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#invariance"},{"categories":["java"],"collections":null,"content":"Covariance in Arrays The examples above illustrate covariance and contravariance in Java’s generic classes, which should now be clear. Beyond generics, Java also supports covariance or contravariance in other areas. Let’s briefly discuss one. First, arrays in Java support covariance. This means an array of type Dog is a subtype of an array of type Animal, so the following code compiles successfully. Animal[] animals = new Dog[3]; animals[0] = new Dog(); animals[1] = new Dog(); // failed animals[2] = new Cat(); The above code can be compiled, but it throws an ArrayStoreException when run. Upon closer inspection, the issue lies in the last line. At this point, the object we are manipulating is an Animal array, and we naturally assume that any Animal object can be added to the array. However, this is not feasible because the underlying array is actually of type Dog, so we cannot add a Cat to the array. When the program executes this line, it throws an exception. Java arrays support covariance, but it is not safe because attempting to add inappropriate elements to the array (such as trying to add a cat to a group of dogs) will throw an exception at runtime. This actually touches on another issue: mutability and immutability. If a collection is mutable, we can continue to add, delete, query, or modify its elements after its creation. Conversely, if a collection is immutable, it becomes read-only after creation, and we cannot make changes to it. Some programming languages have both mutable and immutable collections, so we know that using covariance on these immutable collections is very safe. ","date":"2023-05-07","heading":"Covariance in Arrays","objectID":"/posts/understanding-variance-in-10-minutes/:2:6","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#covariance-in-arrays"},{"categories":["java"],"collections":null,"content":"Immutability of Collections Now let\u0026rsquo;s look at how the Java standard library handles variance with collections. Unsurprisingly, the following code fails to compile, meaning that collections in Java are invariant. Of course, in the example above, we have already demonstrated the invariance of collections. // failed List animals = new ArrayList(); ","date":"2023-05-07","heading":"Immutability of Collections","objectID":"/posts/understanding-variance-in-10-minutes/:2:7","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#immutability-of-collections"},{"categories":["java"],"collections":null,"content":"Covariance in Collections To use covariance in collections, the extends syntax is also required. This allows us to safely treat a group of cats as a group of animals. Note that in this case, the identity of this group of animals is that of a producer—we can read its elements but cannot add or modify them, meaning this covariant collection is read-only. List\u003c? extends Animal\u003e animals = List.of(new Cat(), new Cat()); Animal animal1 = animals.get(0); // This will cause an error—read-only, no writing allowed // animals.add(new Cat()); Of course, this read-only restriction only applies to the use of generic parameters. We can still call methods like clear() on the collection to empty it, as this method does not involve any generic parameters. ","date":"2023-05-07","heading":"Covariance in Collections","objectID":"/posts/understanding-variance-in-10-minutes/:2:8","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#covariance-in-collections"},{"categories":["java"],"collections":null,"content":"Contravariance in Collections In contrast to covariance, applying contravariance to a collection means it acts as a consumer—write-only, no reading allowed. If you forcibly attempt to read from it, you can only obtain an Object type. List\u003c? super Cat\u003e cats = new ArrayList(); cats.add(new Cat()); cats.add(new Cat()); Object o = cats.get(0); So, some experts have summarized the PECS principle, which stands for Producer-Extends, Consumer-Super. Producers use the extends keyword, while consumers use the super keyword. In the Java standard library, there is a method that perfectly illustrates the PECS principle—the copy method in the java.util.Collections class. The source code is as follows. It can be seen that the purpose of this method is to copy collections, and it uses two types of generic declarations in its parameters. This allows us to copy collections very flexibly. For example, we can copy a List\u0026lt;PetDog\u0026gt; into a List\u0026lt;Object\u0026gt;. public static void copy(List\u003c? super T\u003e dest, List\u003c? extends T\u003e src) { int srcSize = src.size(); if (srcSize \u003e dest.size()) throw new IndexOutOfBoundsException(\"Source does not fit in dest\"); if (srcSize \u003c COPY_THRESHOLD || (src instanceof RandomAccess \u0026\u0026 dest instanceof RandomAccess)) { for (int i=0; i","date":"2023-05-07","heading":"Contravariance in Collections","objectID":"/posts/understanding-variance-in-10-minutes/:2:9","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#contravariance-in-collections"},{"categories":["java"],"collections":null,"content":"Summary Alright, now everyone should have a complete understanding of the concept of variance. Let me summarize it in slightly more formal terms: If A is a subtype of B, it is denoted as A \u0026lt;= B. If T is a generic type and U is its parameter, it is denoted as T\u0026lt;U\u0026gt;. If for A \u0026lt;= B, we have T\u0026lt;A\u0026gt; \u0026lt;= T\u0026lt;B\u0026gt;, this is called covariance; If for A \u0026lt;= B, we have T\u0026lt;A\u0026gt; =\u0026gt; T\u0026lt;B\u0026gt;, this is called contravariance; If for A \u0026lt;= B, there is neither covariance nor contravariance, this is called invariance. In Java, covariance is declared using \u0026lt;? extends T\u0026gt;, representing any type T or a subtype of T. The extends keyword bounds the parent type of the generic parameter, hence it is also called the upper bound wildcard. Contravariance is declared using \u0026lt;? super T\u0026gt;, representing any type T or a supertype of T. The super keyword bounds the child type of the generic parameter, hence it is also called the lower bound wildcard. Typically, covariant parameters appear in the return position of functions, allowing the return of more specific types, while contravariant parameters appear in the parameter position of functions, accepting more general types. That’s all for the introduction to variance. Later, I’ll supplement this with generics and variance in other languages, which are also quite interesting. Java codes in this article could be found here. ","date":"2023-05-07","heading":"Summary","objectID":"/posts/understanding-variance-in-10-minutes/:3:0","tags":["java"],"title":"Understanding Covariance and Contravariance in 10 Minutes","uri":"/posts/understanding-variance-in-10-minutes/#summary"},{"categories":["tutorials"],"collections":null,"content":"KeePassXC is a free and open-source password manager with a wealth of powerful features to strongly protect your passwords. If you have registered many accounts, can\u0026rsquo;t remember your passwords, and often need to reset them, you should definitely try KeePassXC. It will ensure you never have to worry about forgetting your passwords again! ","date":"2022-12-26","heading":"","objectID":"/tutorials/keepassxc/:0:0","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#"},{"categories":["tutorials"],"collections":null,"content":"Getting Started ","date":"2022-12-26","heading":"Getting Started","objectID":"/tutorials/keepassxc/:1:0","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#getting-started"},{"categories":["tutorials"],"collections":null,"content":"Download and Install First, go to the official website to download and install KeePassXC. ","date":"2022-12-26","heading":"Download and Install","objectID":"/tutorials/keepassxc/:1:1","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#download-and-install"},{"categories":["tutorials"],"collections":null,"content":"Create a Database Open the KeePassXC software, and you should see a similar interface. Before using it for the first time, you need to create a database. All your passwords will be securely stored in the database in an encrypted form. Even if the database is leaked, your passwords will remain protected. Click the \u0026ldquo;New Database\u0026rdquo; button in the interface to open the dialog. First, give your database a name — there are no strict requirements, just name it as you like. Next, you need to set the database format and encryption settings. The default configuration is optimal, so you don\u0026rsquo;t need to change anything here. Unless you have special requirements, the default decryption time of 1 second is sufficient. A longer decryption time uses more transformation rounds, making the database more secure, but it will also take longer to open and save. If you\u0026rsquo;re interested, you can click \u0026ldquo;Advanced Settings\u0026rdquo; to modify the encryption algorithm and transformation rounds yourself. Different configurations require different amounts of time. After configuring, don\u0026rsquo;t forget to click the \u0026ldquo;Benchmark 1 Second Delay\u0026rdquo; button to calculate the number of rounds needed for a 1-second delay based on your pc specs and device performance. Finally, you need to set a password for the database. Every time you use KeePassXC, you must enter this password to open the database. If you forget the password, you will permanently lose access to the database, so be sure to remember it! If you are sure that no one else uses your computer and you don\u0026rsquo;t want to enter a password every time you open KeePassXC, you can use a \u0026ldquo;lazy\u0026rdquo; method, but this will reduce security. This method is to use only a key file to encrypt the database. In the password setup dialog, select \u0026ldquo;Add Additional Protection,\u0026rdquo; choose \u0026ldquo;Add Key File,\u0026rdquo; then click \u0026ldquo;Generate\u0026rdquo; to create a key file with the .keyx extension. If you use only this file to encrypt the database, you won\u0026rsquo;t need to enter a password next time — just click once. However, if someone else uses your computer, they can also access all your account information without a password. Of course, for greater security, you can use both a password and a key file to encrypt the database. In this case, losing either one will make your database permanently inaccessible. So, regardless of the method, I recommend making backups — never put all your eggs in one basket. After setting the password or key file, click Finish button. A dialog will pop up to choose the save location for the database. It\u0026rsquo;s recommended to save the database in a cloud sync folder like OneDrive. This way, after modifying and saving the database, it will automatically sync to the cloud. Even if the local database is lost, you can quickly restore it from the cloud. Note If your device supports Windows Hello, just use a password to encrypt the database and enable Quick Unlock in the settings. You can then quickly unlock the database using fingerprint or facial recognition. This is currently the most convenient and secure method, as long as your device supports it. ","date":"2022-12-26","heading":"Create a Database","objectID":"/tutorials/keepassxc/:1:2","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#create-a-database"},{"categories":["tutorials"],"collections":null,"content":"Create Groups To keep your passwords organized, it\u0026rsquo;s best to group them by purpose. Right-click the root group and select \u0026ldquo;New Group,\u0026rdquo; then assign a name, and add notes or an icon if needed. Once groups are set up, you can categorize your passwords for easier management and retrieval. ","date":"2022-12-26","heading":"Create Groups","objectID":"/tutorials/keepassxc/:1:3","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#create-groups"},{"categories":["tutorials"],"collections":null,"content":"Save Passwords The primary function of KeePassXC is to save passwords. Select a group, then click Entry → New Entry in the menu bar or the Add New Entry button in the toolbar to open the add entry dialog. Fill in the password details as follows: Note You can get a program\u0026rsquo;s title by checking the window title or viewing it in Task Manager. Title: The name of the password entry. For auto-type, the title must match the target program\u0026rsquo;s window title. Username: The username to save. If the database already has the same username, it can auto-complete. Password: The password to save. Click the square button next to the password field to launch the password generator. The line below shows password strength with colors. URL: The website address for the account. Click the download button to fetch the site\u0026rsquo;s favicon as the entry\u0026rsquo;s icon. Tags: Add tags to the entry (English only). Expiry Date: Set a password expiry date if needed. Usually not required unless you have higher security needs. Notes: Add any additional notes if necessary. ","date":"2022-12-26","heading":"Save Passwords","objectID":"/tutorials/keepassxc/:1:4","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#save-passwords"},{"categories":["tutorials"],"collections":null,"content":"Using Passwords After saving passwords, how do you use them? It\u0026rsquo;s simple: open KeePassXC, find the entry you need, right-click it, and select Copy Username or Copy Password to paste them where needed. You can also use keyboard shortcuts or toolbar icons (the third group of icons in the toolbar). The default shortcuts are Ctrl+B and Ctrl+C. Many programs can access the clipboard. To prevent accidental password leaks from copying and pasting, KeePassXC uses a timeout mechanism. By default, the clipboard is cleared after 10 seconds to protect your passwords. You can see the countdown in the status bar at the bottom right. ","date":"2022-12-26","heading":"Using Passwords","objectID":"/tutorials/keepassxc/:1:5","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#using-passwords"},{"categories":["tutorials"],"collections":null,"content":"Auto-Type KeePassXC also has a powerful auto-type feature for automatically filling in usernames and passwords. First, open the settings via Tools → Settings or the gear icon in the toolbar. Go to General → Auto-Type, assign a global auto-type shortcut (e.g., Ctrl+Alt+A), and click Save. By default, a dialog will pop up before each auto-type. You can uncheck Always ask before performing auto-type in settings so that if only one matching entry exists, auto-type will start immediately. If there are multiple matches, the dialog will appear. You can also modify the auto-type sequence in two ways: in the entry editor for individual entries, or by right-clicking a group and selecting Edit Group to apply a sequence to all entries in that group. To change the sequence for all entries, edit the root group\u0026rsquo;s settings. My sequence is {CLEARFIELD}{USERNAME}{TAB}{CLEARFIELD}{PASSWORD}, which clears the username and password fields before auto-typing. You can also add {ENTER} at the end to submit the form after auto-type. If you don\u0026rsquo;t want to change it, you can use the default sequence: {USERNAME}{TAB}{PASSWORD}{ENTER}. This means: enter the username, press TAB to switch to the password field, enter the password, and press Enter to confirm. Note You can view all auto-type codes in the official reference manual. After everything is set up, it\u0026rsquo;s time to witness the magic! In the target program, click the input field, then press the shortcut you set (e.g., Ctrl+Alt+A), and KeePassXC will elegantly auto-type your password! Warning Auto-type does not work in all programs (such as QQ); only those that support clipboard pasting can use auto-type. ","date":"2022-12-26","heading":"Auto-Type","objectID":"/tutorials/keepassxc/:1:6","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#auto-type"},{"categories":["tutorials"],"collections":null,"content":"Advanced Tips ","date":"2022-12-26","heading":"Advanced Tips","objectID":"/tutorials/keepassxc/:2:0","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#advanced-tips"},{"categories":["tutorials"],"collections":null,"content":"Password Generator Click Tools → Password Generator in the menu or the fourth square icon in the toolbar to open the password generator. You can also open it from the password field when creating or editing an entry. The password generator can create strong random passwords, which you can save in KeePassXC or copy elsewhere. The two buttons next to the password field are Regenerate Password and Copy to Clipboard. If you\u0026rsquo;re not satisfied with the generated password, use controls below to change the length and character types. Warning The password generator does not save history. Once you close the window and reopen it, the generated password will change. If you need to copy the same password multiple times, save it as an entry first to avoid losing it. ","date":"2022-12-26","heading":"Password Generator","objectID":"/tutorials/keepassxc/:2:1","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#password-generator"},{"categories":["tutorials"],"collections":null,"content":"Browser Integration Browsers are also desktop applications, so KeePassXC can auto-fill in browsers as well. In this case, set the entry\u0026rsquo;s title to the web page\u0026rsquo;s title. You still need to add the entry in KeePassXC first. What if you want to automatically save passwords entered in the browser to KeePassXC? KeePassXC provides browser integration for this. First, install the browser extension from the Chrome Web Store or Edge Store. Then, open the settings via the gear icon in the toolbar, enable Browser Integration, and check the browsers you want to integrate under Enable integration for these browsers. Go back to your browser, click the newly installed KeePassXC extension, and click Connect. Enter a name for the browser. When KeePassXC turns green, the extension is successfully connected. Now, when you fill in passwords in the browser, you\u0026rsquo;ll be prompted to save them to KeePassXC. ","date":"2022-12-26","heading":"Browser Integration","objectID":"/tutorials/keepassxc/:2:2","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#browser-integration"},{"categories":["tutorials"],"collections":null,"content":"Disable Screen Capture Protection KeePassXC enables screen capture protection by default. When taking screenshots, recording, streaming, or remote controlling, the KeePassXC window will appear black. Sometimes you may want to temporarily disable this feature, such as when writing a tutorial. To disable it, simply start KeePassXC with the --allow-screencapture parameter. Find the KeePassXC shortcut on your desktop, right-click and select Properties, and add the parameter at the end of the Target field. If the path contains spaces, the path will be in quotes — make sure to add the parameter outside the quotes, after a space. ","date":"2022-12-26","heading":"Disable Screen Capture Protection","objectID":"/tutorials/keepassxc/:2:3","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#disable-screen-capture-protection"},{"categories":["tutorials"],"collections":null,"content":"Reference Entries Sometimes you need to use the same username or password in multiple URLs or programs. Maintaining multiple identical entries is cumbersome. KeePassXC provides a reference entry feature to solve this. Right-click the entry you want to copy, then click Clone Entry. In the dialog, check Replace username and password with references and click OK. Modify the cloned entry as needed; note that its username and password are references and don\u0026rsquo;t need to be changed. Changing the original entry will automatically update all references. ","date":"2022-12-26","heading":"Reference Entries","objectID":"/tutorials/keepassxc/:2:4","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#reference-entries"},{"categories":["tutorials"],"collections":null,"content":"TOTP Some websites support TOTP-based two-factor authentication, and KeePassXC can also store TOTP secrets. When enabling two-factor authentication, the website usually provides a QR code or secret. Right-click the entry, select TOTP → Set up TOTP, and paste the secret from the website into the Secret field, then click OK. You can now get TOTP codes from KeePassXC. If the website only provides a QR code, you can install the Authenticator: 2FA Client extension in your browser, scan the QR code and then retrieve secret from it. Warning TOTP codes are time-based. Make sure your system time is accurate, or the codes won\u0026rsquo;t work. ","date":"2022-12-26","heading":"TOTP","objectID":"/tutorials/keepassxc/:2:5","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#totp"},{"categories":["tutorials"],"collections":null,"content":"Add More Attributes to Entries Previously, we only filled in the basic fields for password entries. If your account needs to store more attributes, KeePassXC supports this as well. When editing an entry, select the Advanced tab on the left to access more detailed fields. Here you can add more account-related information. If you need to keep information confidential, check the Protect box to hide it. To view hidden information, click the Show/Hide button below the checkbox. You can even add attachments, such as images of documents in the entry. KeePassXC can save them for you. ","date":"2022-12-26","heading":"Add More Attributes to Entries","objectID":"/tutorials/keepassxc/:2:6","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#add-more-attributes-to-entries"},{"categories":["tutorials"],"collections":null,"content":"Other Settings KeePassXC has many configuration options you can enable as needed. To optimize startup behavior, select Startup and enable Automatically launch KeePassXC at system startup and Minimize window after unlocking database. Clicking the close button will exit KeePassXC by default. To keep it in the system tray, enable Minimize instead of app exit, Show system tray icon, and Hide windows to system tray when minimized under User Interface. If your system supports Windows Hello or Touch ID, you can enable Quick Unlock. Next time you unlock the database, you can use fingerprint or facial recognition instead of entering a password. ","date":"2022-12-26","heading":"Other Settings","objectID":"/tutorials/keepassxc/:2:7","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#other-settings"},{"categories":["tutorials"],"collections":null,"content":"Run as Administrator at Startup By default, KeePassXC starts with normal privileges and cannot perform auto-type in windows running as administrator. To use auto-type in such windows, KeePassXC itself must also run as administrator. However, KeePassXC\u0026rsquo;s built-in auto-start only runs with normal privileges. To run KeePassXC as administrator at startup, use Task Scheduler. First, disable KeePassXC\u0026rsquo;s built-in auto-start. Then, open an administrator Command Prompt or PowerShell window and run the following command to create a scheduled task for KeePassXC. Replace KeePassXC.exe with the absolute path to KeePassXC. If the path contains spaces, enclose it in double quotes. schtasks /create /tn keepassxc /tr \"KeePassXC.exe\" /sc ONLOGON /rl HIGHEST After creating the task, run the following command to start KeePassXC. If it opens successfully, the scheduled task was created correctly, and KeePassXC will auto-start next time you log in. schtasks /run /tn keepassxc If nothing happens, the path to KeePassXC is incorrect. Delete the task and recreate it. schtasks /delete /tn keepassxc ","date":"2022-12-26","heading":"Run as Administrator at Startup","objectID":"/tutorials/keepassxc/:2:8","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#run-as-administrator-at-startup"},{"categories":["tutorials"],"collections":null,"content":"Summary Through the above introduction, I believe you should now be familiar with the various usage methods of KeePassXC. I believe it will bring you great convenience in password management in the future. I will also continue to share various tutorials in the future. See you next time! ","date":"2022-12-26","heading":"Summary","objectID":"/tutorials/keepassxc/:3:0","tags":["security"],"title":"KeePassXC Password Manager","uri":"/tutorials/keepassxc/#summary"},{"categories":["tutorials"],"collections":null,"content":"Friends who often install operating systems should be very familiar with making bootable drives, as it is a necessary step every time an operating system is installed. If the operating system to be installed is different from the previous one, then the bootable drive has to be made again. Is there any tool that can get this job done once and for all, thus bidding farewell to the complicated steps of making a bootable drive? There really is such a tool, which is the \u0026ldquo;bootable drive making magic tool Ventoy\u0026rdquo; to be introduced in this article! ","date":"2022-12-11","heading":"","objectID":"/tutorials/ventoy/:0:0","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#"},{"categories":["tutorials"],"collections":null,"content":"Download Ventoy Although Ventoy can also be downloaded from its official website, the speed is very slow. It is recommended to download it through the Github link. When downloading, choose the proper compressed package for you system(I think for most people the choice would be windows). After the download is completed, extract the file. Locate Ventoy2Disk.exe, and you can launch the Ventoy main program. ","date":"2022-12-11","heading":"Download Ventoy","objectID":"/tutorials/ventoy/:1:0","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#download-ventoy"},{"categories":["tutorials"],"collections":null,"content":"Feature Introduction When you open Ventoy, you should see an interface similar to the following figure. Some function items can be set in the menu bar. The device section lists the current USB drive devices. If they are not displayed, you can unplug and replug the USB drive, and then click the refresh button on the right to check. The section below the device lists the current local Ventoy version and the Ventoy version installed in the USB drive. If you have downloaded a new version of Ventoy, you can click the Upgrade Button below to update the version in the USB drive. ","date":"2022-12-11","heading":"Feature Introduction","objectID":"/tutorials/ventoy/:2:0","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#feature-introduction"},{"categories":["tutorials"],"collections":null,"content":"Getting Started ","date":"2022-12-11","heading":"Getting Started","objectID":"/tutorials/ventoy/:3:0","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#getting-started"},{"categories":["tutorials"],"collections":null,"content":"Creating a Bootable USB Drive First, insert your USB drive and open Ventoy. If you\u0026rsquo;re using it for the first time, only the local Ventoy version should be displayed. Click on the Configuration Options menu, select Secure Boot Support, and then click the installation button below to create a Ventoy bootable USB drive. Note that this operation will erase all data on the USB drive. Make sure there is no important data on the USB drive before proceeding. If Ventoy has been installed previously and you want to upgrade it, there\u0026rsquo;s no need to worry. Ventoy upgrades will not affect local data, and you can upgrade it directly. ","date":"2022-12-11","heading":"Creating a Bootable USB Drive","objectID":"/tutorials/ventoy/:3:1","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#creating-a-bootable-usb-drive"},{"categories":["tutorials"],"collections":null,"content":"Adding System Images Okay, now the Ventoy bootable USB drive has been created. That\u0026rsquo;s it? Yes, it\u0026rsquo;s that simple! Now open the file manager, and you should see a USB drive named Ventoy. This is your bootable drive. What you need to do next is simple. Just directly copy all the system image files you need (with the file extension .iso) to the USB drive. You can place multiple versions of Windows systems such as Win7, Win10, Win11, and PE at the same time, and even Linux can be put in. Ventoy supports them all! You no longer need to frequently create system bootable drives! Moreover, the USB drive can not only store image files. You can completely use it as an ordinary USB drive to store music, videos, documents, etc. Only when the computer is restarted and the USB drive is set as the first boot item in BIOS will it be used as a bootable drive. ","date":"2022-12-11","heading":"Adding System Images","objectID":"/tutorials/ventoy/:3:2","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#adding-system-images"},{"categories":["tutorials"],"collections":null,"content":"Booting Restart the computer and enter the BIOS. Set Ventoy as the first boot item. Then you can enter the Ventoy boot interface. All the system images in Ventoy will be listed here. Use the cursor keys to select the image you want to boot and press Enter to start the system image. ","date":"2022-12-11","heading":"Booting","objectID":"/tutorials/ventoy/:3:3","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#booting"},{"categories":["tutorials"],"collections":null,"content":"Extended Reading ","date":"2022-12-11","heading":"Extended Reading","objectID":"/tutorials/ventoy/:4:0","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#extended-reading"},{"categories":["tutorials"],"collections":null,"content":"System Download It is recommended to download the Windows image file on Microsoft Official Website. Since systems such as Win7 and Win8 have ceased to be supported, only Win10 and Win11 systems are recommended for download and installation at present. After the download is completed, put the system image into the Ventoy boot disk, and then it can be started. After the download is completed, it is recommended to verify the image file first to ensure its accuracy before installation. ","date":"2022-12-11","heading":"System Download","objectID":"/tutorials/ventoy/:4:1","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#system-download"},{"categories":["tutorials"],"collections":null,"content":"Verifying the Image When downloading relatively large files such as system images, the download provider usually provides a set of verification codes, which look like a string of random characters. Its function is to confirm whether the downloaded file is complete and accurate. With the relatively good network conditions nowadays, the probability of download errors is relatively low, but transmission problems may still occur, resulting in errors in the downloaded file. For safety\u0026rsquo;s sake, if the official provides a verification code, it is best to verify it after the download is completed to ensure that the file has no errors. If the file is incorrect, it can only be downloaded again. Here, the graphical tool fhash is recommended, which can be installed directly from the Microsoft Store. After the installation is completed, drag the large file to be verified in. After the calculation result comes out, compare it with the verification code provided by the official. If they are exactly the same, it means the file is fine. MD5, SHA1 and SHA256 are all types of verification algorithms, and it is sufficient to compare any one of them. ","date":"2022-12-11","heading":"Verifying the Image","objectID":"/tutorials/ventoy/:4:2","tags":["windows"],"title":"Using Ventoy to Create a Universal Bootable USB Drive","uri":"/tutorials/ventoy/#verifying-the-image"},{"categories":null,"collections":null,"content":"A lazy guy. ","date":"2020-04-22","heading":"","objectID":"/intro/:0:0","tags":null,"title":"Introduction","uri":"/intro/#"}]