# 2. Asynchronous Programming:

## 2. Asynchronous Programming:

Using `async` and `await` for concurrent tasks with asyncio.

***

**1. Basic Async Function**

```python
import asyncio

async def say_hello():
    print("Hello, World!")
    await asyncio.sleep(1)
    print("Goodbye, World!")

asyncio.run(say_hello())
```

***

**2. Running Multiple Tasks Concurrently**

```python
import asyncio

async def task1():
    print("Task 1 starting...")
    await asyncio.sleep(2)
    print("Task 1 done!")

async def task2():
    print("Task 2 starting...")
    await asyncio.sleep(1)
    print("Task 2 done!")

async def main():
    await asyncio.gather(task1(), task2())

asyncio.run(main())
```

***

**3. Using `asyncio.sleep` for Delays**

```python
import asyncio

async def countdown(n: int):
    while n > 0:
        print(f"Counting down: {n}")
        await asyncio.sleep(1)
        n -= 1
    print("Countdown finished!")

asyncio.run(countdown(5))
```

***

**4. Concurrent HTTP Requests with `aiohttp`**

```python
import aiohttp
import asyncio

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = ["https://example.com", "https://httpbin.org", "https://python.org"]
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        for i, content in enumerate(results):
            print(f"Content from URL {i + 1}:\n{content[:100]}...\n")

asyncio.run(main())
```

***

**5. Producer-Consumer Pattern**

```python
import asyncio

async def producer(queue):
    for i in range(5):
        print(f"Producing {i}")
        await queue.put(i)
        await asyncio.sleep(1)

async def consumer(queue):
    while True:
        item = await queue.get()
        print(f"Consuming {item}")
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    await asyncio.gather(producer(queue), consumer(queue))

asyncio.run(main())
```

***

**6. Timeouts with `asyncio.wait_for`**

```python
import asyncio

async def slow_task():
    await asyncio.sleep(5)
    return "Task completed!"

async def main():
    try:
        result = await asyncio.wait_for(slow_task(), timeout=2)
        print(result)
    except asyncio.TimeoutError:
        print("Task timed out!")

asyncio.run(main())
```

***

**7. Asynchronous File I/O with `aiofiles`**

```python
import aiofiles
import asyncio

async def write_to_file():
    async with aiofiles.open("example.txt", "w") as f:
        await f.write("Hello, Async File I/O!\n")

async def read_from_file():
    async with aiofiles.open("example.txt", "r") as f:
        content = await f.read()
        print(content)

async def main():
    await write_to_file()
    await read_from_file()

asyncio.run(main())
```

***

**8. Using `asyncio.Semaphore` to Limit Concurrency**

```python
import asyncio

async def limited_task(sem, i):
    async with sem:
        print(f"Task {i} starting...")
        await asyncio.sleep(2)
        print(f"Task {i} done!")

async def main():
    sem = asyncio.Semaphore(2)  # Limit to 2 concurrent tasks
    tasks = [limited_task(sem, i) for i in range(5)]
    await asyncio.gather(*tasks)

asyncio.run(main())
```

***

**9. Event Loop with `asyncio.create_task`**

```python
import asyncio

async def print_numbers():
    for i in range(5):
        print(f"Number: {i}")
        await asyncio.sleep(1)

async def main():
    task = asyncio.create_task(print_numbers())
    print("Task created. Doing something else...")
    await asyncio.sleep(3)
    print("Main task completed.")
    await task

asyncio.run(main())
```

***

**10. Using `asyncio.Queue` for Communication Between Tasks**

```python
import asyncio

async def producer(queue):
    for i in range(5):
        print(f"Producer: putting {i} in queue")
        await queue.put(i)
        await asyncio.sleep(1)

async def consumer(queue):
    while True:
        item = await queue.get()
        print(f"Consumer: got {item} from queue")
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    await asyncio.gather(producer(queue), consumer(queue))

asyncio.run(main())
```

***
