精华内容
下载资源
问答
  • TAP 异步编程

    2021-11-05 13:54:20
    所以如果一个 TAP 方法内部使用 Task 构造函数来实例化要返回的 Task,那么 TAP 方法必须在返回 Task 对象之前对其调用 Start。 8任务取消 在 TAP 中,取消对于异步方法实现者和消费者来说都是可选的。如果一...

    我们的应用程序广泛使用文件和网络 I/O 操作,I/O 相关 API 传统上默认是阻塞的,导致用户体验和硬件利用率不佳,此类问题的编码难度也较大。

    解决此类问题需要使用异步编程,异步强调的是非阻塞,是一种编程模式,主要解决了因文件、网络等 I/O 操作阻塞主线程工作的问题,比如阻塞期间 UI 无法响应问题。

    而异步编程又可以借助多线程技术来解决。前面我们讲了基于 System.Threading 命名空间的多线程编程,该命名空间提供的类型是直接和线程相关的 API,虽然可以用来实现异步操作,但有些繁琐。随着 .NET 的发展,.NET 对多线程编程相继做了进一步的抽象封装,引入了 System.Threading.Tasks 命名空间,使多线程异步编程更简单易懂。

    异步编程主要有如下用途:

    • 在等待 I/O 请求返回的过程中,通过让出线程使其能处理更多的服务器请求。

    • 在等待 I/O 请求时让出线程使其继续进行 UI 交互,并将需要长时间运行的工作过渡到其他 CPU 线程,使用户界面的响应性更强。

    使用 .NET 基于 Task 的异步模型可以直接编写 I/O 受限和 CPU 受限的异步代码。该模型围绕着 Task 和 Task<T> 类型以及 C# 的 async 和 await 关键字展开。本文将讲解如何使用 .NET 异步编程及一些常见的异步编程操作。

    1Task 和 Task<T>

    Task 是 Promise 模型的实现。简单说,它给出“承诺(Promise)”:会在稍后完成工作。而 .NET 的 Task 是为了简化使用“Promise”而设计的 API。

    Task 表示不返回值的操作,Task<T> 表示返回 T 类型的值的操作。

    重要的是要把 Task 理解为发起异步工作的抽象,而不是对线程的抽象。默认情况下,Task 在当前线程上执行,并酌情将工作委托给操作系统。可以选择通过 Task.Run API 明确要求任务在单独的线程上运行。

    Task 提供了一个 API 协议,用于监视、等待和访问任务的结果值。比如,通过 await 关键字等待任务执行完成,为使用 Task 提供了更高层次的抽象。

    使用 await 允许你在任务运行期间执行其它有用的工作,将线程的控制权交给其它调用者,直到自己的任务完成。你不再需要依赖回调或事件来在任务完成后继续执行后续工作。

    2Task 的状态

    虽然实际 TAP 编程中很少使用到 Task 的状态,但它是很多异步操作机理的基础。Task 类为异步操作提供了一个生命周期,这个周期由 TaskStatus 枚举表示,它有如下值:

    public enum TaskStatus
    {
        Created = 0,
        WaitingForActivation = 1,
        WaitingToRun = 2,
        Running = 3,
        WaitingForChildrenToComplete = 4,
        RanToCompletion = 5,
        Canceled = 6,
        Faulted = 7
    }

    其中 CanceledFaulted 和 RanToCompletion 状态一起被认为是任务的最终状态。因此,如果任务处于最终状态,则其 IsCompleted 属性为 true 值。

    3I/O 受限异步操作

    下面示例代码演示了一个典型的异步 I/O 调用操作:

    public Task<string> GetHtmlAsync()
    {
        // 此处是同步执行
        var client = new HttpClient();
        return client.GetStringAsync("https://www.dotnetfoundation.org");
    }

    这个例子调用了一个异步方法,并返回了一个活动的 Task,它很可能还没有完成。

    下面第二个代码示例增加了asyncawait关键字对任务进行操作:

    public async Task<string> GetFirstCharactersCountAsync(string url, int count)
    {
        // 此处是同步执行
        var client = new HttpClient();
    
        // 此处 await 挂起代码的执行,把控制权交出去(线程可以去做别的事情)
        var page = await client.GetStringAsync("https://www.dotnetfoundation.org");
    
        // 任务完成后恢复了控制权,继续执行后续代码
        // 此处回到了同步执行
    
        if (count > page.Length)
        {
            return page;
        }
        else
        {
            return page.Substring(0, count);
        }
    }

    使用 await 关键字告诉当前上下文赶紧生成快照并交出控制权,异步任务执行完成后会带着返回值去线程池排队等待可用线程,等到可用线程后,恢复上下文,线程继续执行后续代码。

    GetStringAsync() 方法的内部通过底层 .NET 库调用资源(也许会调用其他异步方法),一直到 P/Invoke 互操作调用本地(Native)网络库。本地库随后可能会调用到一个系统 API(如 Linux 上 Socket 的write()API)。Task 对象将通过层层传递,最终返回给初始调用者。

    在整个过程中,关键的一点是,没有一个线程是专门用来处理任务的。虽然工作是在某种上下文中执行的(操作系统确实要把数据传递给设备驱动程序并中断响应),但没有线程专门用来等待请求的数据回返回。这使得系统可以处理更大的工作量,而不是干等着某个 I/O 调用完成。

    虽然上面的工作看似很多,但与实际 I/O 工作所需的时间相比,简直微不足道。用一条不太精确的时间线来表示,大概是这样的:

    0-1--------------------2-3

    01所花费的时间是await交出控制权之前所花的时间。从12花费的时间是GetStringAsync方法花费在 I/O 上的时间,没有 CPU 成本。最后,从23花费的时间是上下文重新获取控制权后继续执行的时间。

    4CPU 受限异步操作

    CPU 受限的异步代码与 I/O 受限的异步代码有些不同。因为工作是在 CPU 上完成的,所以没有办法绕开专门的线程来进行计算。使用 async 和 await 只是为你提供了一种干净的方式来与后台线程进行交互。请注意,这并不能为共享数据提供加锁保护,如果你正在使用共享数据,仍然需要使用适当的同步策略。

    下面是一个 CPU 受限的异步调用:

    public async Task<int> CalculateResult(InputData data)
    {
        // 在线程池排队获取线程来处理任务
        var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));
    
        // 此时此处,你可以并行地处理其它工作
    
        var result = await expensiveResultTask;
    
        return result;
    }

    CalculateResult方法在它被调用的线程(一般可以定义为主线程)上执行。当它调用Task.Run时,会在线程池上排队执行 CPU 受限操作 DoExpensiveCalculation,并接收一个Task<int>句柄。DoExpensiveCalculation会在下一个可用的线程上并行运行,很可能是在另一个 CPU 核上。和 I/O 受限异步调用一样,一旦遇到awaitCalculateResult的控制权就会被交给它的调用者,这样在DoExpensiveCalculation返回结果的时候,结果就会被安排在主线程上排队运行。

    对于开发者,CPU 受限和 I/O 受限的在调用方式上没什么区别。区别在于所调用资源性质的不同,不必关心底层对不同资源的调用的具体逻辑。编写代码需要考虑的是,对于 CPU 受限的异步任务,根据实际情况考虑是否需要使其和其它任务并行执行,以加快程序的整体运行时间。

    5异步编程模式

    最后简单回顾一下 .NET 历史上提供的三种执行异步操作的模式。

    • 基于任务的异步模式(Task-based Asynchronous Pattern,TAP),它使用单一的方法来表示异步操作的启动和完成。TAP 是在 .NET Framework 4 中引入的。它是 .NET 中异步编程的推荐方法。C# 中的 async 和 await 关键字为 TAP 添加了语言支持。

    • 基于事件的异步模式(Event-based Asynchronous Pattern,EAP),这是基于事件的传统模式,用于提供异步行为。它需要一个具有 Async 后缀的方法和一个或多个事件。EAP 是在 .NET Framework 2.0 中引入的。它不再被推荐用于新的开发。

    • 异步编程模式(Asynchronous Programming Model,APM)模式,也称为 IAsyncResult 模式,这是使用 IAsyncResult 接口提供异步行为的传统模式。在这种模式中,需要BeginEnd方法同步操作(例如,BeginWriteEndWrite来实现异步写操作)。这种模式也不再推荐用于新的开发。

    下面简单举例对三种模式进行比较。

    假设有一个 Read 方法,该方法从指定的偏移量开始将指定数量的数据读入提供的缓冲区:

    public class MyClass
    {
        public int Read(byte [] buffer, int offset, int count);
    }

    若用 TAP 异步模式来改写,该方法将是简单的一个 ReadAsync 方法:

    public class MyClass
    {
        public Task<int> ReadAsync(byte [] buffer, int offset, int count);
    }

    若使用 EAP 异步模式,需要额外多定义一些类型和成员:

    public class MyClass
    {
        public void ReadAsync(byte [] buffer, int offset, int count);
        public event ReadCompletedEventHandler ReadCompleted;
    }
    
    public delegate void ReadCompletedEventHandler(
        object sender, ReadCompletedEventArgs e);
    
    public class ReadCompletedEventArgs : AsyncCompletedEventArgs
    {
        public MyReturnType Result { get; }
    }

    若使用 AMP 异步模式,则需要定义两个方法,一个用于开始执行异步操作,一个用于接收异步操作结果:

    public class MyClass
    {
        public IAsyncResult BeginRead(
            byte [] buffer, int offset, int count,
            AsyncCallback callback, object state);
        public int EndRead(IAsyncResult asyncResult);
    }

    后两种异步模式已经过时不推荐使用了,这里也不再继续探讨。年长的 .NET 程序员可能比较熟悉后两种异步模式,毕竟那时候没有 async/await,应该没少折腾。

    下面来介绍几个常见的基于 TAP 的异步操作。

    6手动控制任务启动

    为了支持手动控制任务启动,并支持构造与调用的分离,Task 类提供了一个 Start 方法。由 Task 构造函数创建的任务被称为冷任务,因为它们的生命周期处于 Created 状态,只有该实例的 Start 方法被调用才会启动。

    任务状态平时用的情况不多,一般我们在封装一个任务相关的方法时,可能会用到。比如下面这个例子,需要判断某任务满足一定条件才启动:

    static void Main(string[] args)
    {
        MyTask t = new(() =>
        {
            // do something.
        });
    
        StartMyTask(t);
    
        Console.ReadKey();
    }
    
    public static void StartMyTask(MyTask t)
    {
        if (t.Status == TaskStatus.Created && t.Counter>10)
        {
            t.Start();
        }
        else
        {
            // 这里模拟计数业务代码,直到 Counter>10 再执行 Start
            while (t.Counter <= 10)
            {
                // Do something
                t.Counter++;
            }
            t.Start();
        }
    }
    
    public class MyTask : Task
    {
        public MyTask(Action action) : base(action)
        {
        }
    
        public int Counter { get; set; }
    }

    同样,TaskStatus.Created 状态以外的状态,我们叫它热任务,热任务一定是被调用了 Start 方法激活过的。

    7确保任务已激活

    注意,所有从 TAP 方法返回的任务都必须被激活,比如下面这样的代码:

    MyTask task = new(() =>
    {
        Console.WriteLine("Do something.");
    });
    
    // 在其它地方调用
    await task;

    在 await 之前,任务没有执行 Task.Start 激活,await 时程序就会一直等待下去。所以如果一个 TAP 方法内部使用 Task 构造函数来实例化要返回的 Task,那么 TAP 方法必须在返回 Task 对象之前对其调用 Start

    8任务取消

    在 TAP 中,取消对于异步方法实现者和消费者来说都是可选的。如果一个操作允许取消,它就会暴露一个异步方法的重载,该方法接受一个取消令牌(CancellationToken 实例)。按照惯例,参数被命名为 cancellationToken。例如:

    public Task ReadAsync(
        byte [] buffer, int offset, int count,
        CancellationToken cancellationToken)

    异步操作会监控这个令牌是否有取消请求。如果收到取消请求,它可以选择取消操作,如下面的示例通过 while 来监控令牌的取消请求:

    static void Main(string[] args)
    {
        CancellationTokenSource source = new();
        CancellationToken token = source.Token;
    
        var task = DoWork(token);
    
        // 实际情况可能是在稍后的其它线程请求取消
        Thread.Sleep(100);
        source.Cancel();
    
        Console.WriteLine($"取消后任务返回的状态:{task.Status}");
    
        Console.ReadKey();
    }
    
    public static Task DoWork(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            // Do something.
            Thread.Sleep(1000);
    
            return Task.CompletedTask;
        }
        return Task.FromCanceled(cancellationToken);
    }
    

    如果取消请求导致工作提前结束,甚至还没有开始就收到请求取消,则 TAP 方法返回一个以 Canceled 状态结束的任务,它的 IsCompleted 属性为 true,且不会抛出异常。当任务在 Canceled 状态下完成时,任何在该任务注册的延续任务仍都会被调用和执行,除非指定了诸如 NotOnCanceled 这样的选项来选择不延续。

    但是,如果在异步任务在工作时收到取消请求,异步操作也可以选择不立刻结束,而是等当前正在执行的工作完成后再结束,并返回 RanToCompletion 状态的任务;也可以终止当前工作并强制结束,根据实际业务情况和是否生产异常结果返回 Canceled 或 Faulted 状态。

    对于不能被取消的业务方法,不要提供接受取消令牌的重载,这有助于向调用者表明目标方法是否可以取消。

    9进度报告

    几乎所有异步操作都可以提供进度通知,这些通知通常用于用异步操作的进度信息更新用户界面。

    在 TAP 中,进度是通过 IProgress<T> 接口来处理的,该接口作为一个参数传递给异步方法。下面是一个典型的的使用示例:

    static void Main(string[] args)
    {
        var progress = new Progress<int>(n =>
        {
            Console.WriteLine($"当前进度:{n}%");
        });
    
        var task = DoWork(progress);
    
        Console.ReadKey();
    }
    
    public static async Task DoWork(IProgress<int> progress)
    {
        for (int i = 1; i <= 100; i++)
        {
            await Task.Delay(100);
            if (i % 10 == 0)
            {
                progress?.Report(i);
            };
        }
    }

    输出如下结果:

    当前进度:10%
    当前进度:20%
    当前进度:30%
    当前进度:40%
    当前进度:50%
    当前进度:60%
    当前进度:70%
    当前进度:80%
    当前进度:90%
    当前进度:100%

    IProgress<T> 接口支持不同的进度实现,这是由消费代码决定的。例如,消费代码可能只关心最新的进度更新,或者希望缓冲所有更新,或者希望为每个更新调用一个操作,等等。所有这些选项都可以通过使用该接口来实现,并根据特定消费者的需求进行定制。例如,如果本文前面的 ReadAsync 方法能够以当前读取的字节数的形式报告进度,那么进度回调可以是一个 IProgress<long> 接口。

    public Task ReadAsync(
        byte[] buffer, int offset, int count,
        IProgress<long> progress)

    再如 FindFilesAsync 方法返回符合特定搜索模式的所有文件列表,进度回调可以提供工作完成的百分比和当前部分结果集,它可以用一个元组来提供这个信息。

    public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
        string pattern,
        IProgress<Tuple<double, ReadOnlyCollection<List<FileInfo>>>> progress)

    或使用 API 特有的数据类型:

    public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
        string pattern,
        IProgress<FindFilesProgressInfo> progress)

    如果 TAP 的实现提供了接受 IProgress<T> 参数的重载,它们必须允许参数为空,在这种情况下,不会报告进度。IProgress<T> 实例可以作为独立的对象,允许调用者决定如何以及在哪里处理这些进度信息。

    10Task.Yield 让步

    我们先来看一段 Task.Yield() 的代码:

    Task.Run(async () =>
    {
        for(int i=0; i<10; i++)
        {
            await Task.Yield();
            ...
        }
    });

    这里的 Task.Yield() 其实什么也没干,它返回的是一个空任务。那 await 一个什么也没做的空任务有什么用呢?

    我们知道,对计算机来说,任务调度是根据一定的优先策略来安排线程去执行的。如果任务太多,线程不够用,任务就会进入排队状态。而 Yield 的作用就是让出等待的位置,让后面排除的任务先行。它字面上的意思就是让步,当任务做出让步时,其它任务就可以尽快被分配线程去执行。举个现实生活中的例子,就像你在排队办理业务时,好不容易到你了,但你的事情并不急,自愿让出位置,让其他人先办理,自己假装临时有事到外面溜一圈什么事也没干又回来重新排队。默默地做了一次大善人。

    Task.Yield() 方法就是在异步方法中引入一个让步点。当代码执行到让步点时,就会让出控制权,去线程池外面兜一圈什么事也没干再回来重新排队。

    11定制异步任务后续操作

    我们可以对异步任务执行完成的后续操作进行定制。常见的两个方法是 ConfigureAwait 和 ContinueWith

    ConfigureAwait

    我们先来看一段 Windows Form 中的代码:

    private void button1_Click(object sender, EventArgs e)
    {
        var content = CurlAsync().Result;
        ...
    }
    
    private async Task<string> CurlAsync()
    {
        using (var client = new HttpClient())
        {
            returnawait client.GetStringAsync("http://geekgist.com");
        }
    }

    想必大家都知道 CurlAsync().Result 这句代码在 Windows Form 程序中会造成死锁。原因是 UI 主线程执行到这句代码时,就开始等待异步任务的结果,处于阻塞状态。而异步任务执行完后回来准备找 UI 线程继续执行后面的代码时,却发现 UI 线程一直处于“忙碌”的状态,没空搭理回来的异步任务。这就造成了你等我,我又在等你的尴尬局面。

    当然,这种死锁的情况只会在 Winform 和早期的 ASP.NET WebForm 中才会发生,在 Console 和 Web API 应用中不会生产死锁。

    解决办法很简单,作为异步方法调用者,我们只需改用 await 即可:

    private async void button1_Click(object sender, EventArgs e)
    {
        var content = await CurlAsync();
        ...
    }

    在异步方法内部,我们也可以调用任务的 ConfigureAwait(false) 方法来解决这个问题。如:

    private async Task<string> CurlAsync()
    {
        using (var client = new HttpClient())
        {
            returnawait client
                .GetStringAsync("http://geekgist.com")
                .ConfigureAwait(false);
        }
    }

    虽然两种方法都可行,但如果作为异步方法提供者,比如封装一个通用库时,考虑到难免会有新手开发者会使用 CurlAsync().Result,为了提高通用库的容错性,我们就可能需要使用 ConfigureAwait 来做兼容。

    ConfigureAwait(false) 的作用是告诉主线程,我要去远行了,你去做其它事情吧,不用等我。只要先确保一方不在一直等另一方,就能避免互相等待而造成死锁的情况。

    ContinueWith

    ContinueWith 方法很容易理解,就是字面上的意思。作用是在异步任务执行完成后,安排后续要执行的工作。示例代码:

    private void Button1_Click(object sender, EventArgs e)
    {
        var backgroundScheduler = TaskScheduler.Default;
        var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        Task.Factory
            .StartNew(_ => DoBackgroundComputation(), backgroundScheduler)
            .ContinueWith(_ => UpdateUI(), uiScheduler)
            .ContinueWith(_ => DoAnotherBackgroundComputation(), backgroundScheduler)
            .ContinueWith(_ => UpdateUIAgain(), uiScheduler);
    }

    如上,可以一直链式的写下去,任务会按照顺序执行,一个执行完再继续执行下一个。若其中一个任务返回的状态是 Canceled 时,后续的任务也将被取消。这个方法有好些个重载,在实际用到的时候再查看文档即可。

    12小结

    System.Threading.Tasks 命名空间中关键的一个类是 Task 类,基于 Task 的异步 API 和语言级异步编程模式颠覆了传统模式,使得异步编程非常简单。它使我们可以只关注业务层面要处理的任务,而不必关心和使用线程或线程池。重要的是要把 Task 理解为发起异步工作的抽象,而不是对线程的抽象。本文还介绍了 .NET 异步编程模式,而我们现在主流用的都是 TAP 模式,最后本文罗列一些常见的异步操作。

    展开全文
  • html实现tap的多种方式

    2021-06-12 10:42:30
    一、tap.js这是一个比较轻量的插件tap.js,142行代码,支持模块化开发。1)handleEventaddEventListener方法中的第二个参数,我原先并没有注意到其实可以传一个对象,该对象必是实现EventListener接口,查看在线代码...

    一、tap.js

    这是一个比较轻量的插件tap.js,142行代码,支持模块化开发。

    1)handleEvent

    addEventListener方法中的第二个参数,我原先并没有注意到其实可以传一个对象,该对象必是实现EventListener接口,查看在线代码。

    var tap = { handleEvent: function() { alert(this.name); }, name:'tap'};document.getElementById('button').addEventListener('click', tap, false);

    这样一绑定点击就能alert了。并且注意上面的“this”,“this”指向的是“tap”这个对象,而不是这个方法。这种写法有几种好处:

    1. 可以使用“tap”对象中的属性或方法了。

    2. 动态地改变事件处理器,例如tap.handleEvent = xx.yy,而不用先removeEventListener再addEventListener了。

    在插件的129行定义了“Tap.prototype.handleEvent”,在这个方法中通过“switch(e.type)”执行不同的事件。

    2)自定义事件

    插件中使用了两种方式创建自定义事件,新方式CustomEvent与旧方式createEvent,查看在线代码。

    1. CustomEvent

    //CustomEvent方式var tap2 = document.getElementById('button2');tap2.addEventListener('tap', function(e){ alert('custom ' + e.detail);}, false);var evt = new window.CustomEvent('tap', { bubbles: true, cancelable: true, detail: 'tap'});tap2.dispatchEvent(evt);

    “button2”就是个按钮,绑定了个tap事件,注意不是绑定了这个后,点击按钮就会出现alert内容,如果想要点击,还是需要绑定“click”事件的。

    “detail”参数是指当事件初始化时传递的数据,可以传递任何值,“tap2.dispatchEvent()”方法返回一个布尔值。

    如果想点击按钮触发“tap”事件,可以下面这样做:

    tap2.addEventListener('click', function(e){ tap2.dispatchEvent(evt);}, false);

    2. createEvent

    var evt = document.createEvent('Event');evt.initEvent('tap', true, true);

    就创建自定义事件部分不一样,其他地方都差不多。

    插件在“Tap.prototype.end”方法中定义了事件,90行到108行。

    3)使用方式

    插件的prototype总共有方法“leftButton”、“start”、“move”、“end”、“cancel”、“destroy”和“handleEvent”。下面是demo代码:

    Tap event

    在PC上面使用,通过“mousedown”、“mousemove”与“mouseup”来模拟。

    在移动端使用,是通过“touchstart”、“touchmove”、“touchend”与“touchcancel”来模拟。

    1. tap构造函数

    在“tap”构造函数中绑定了“touchstart”和“mousedown”事件,上面点击按钮产生的效果,其实是通过点击“container”来实现的。

    function Tap(el) { this.el = typeof el === 'object' ? el : document.getElementById(el); this.moved = false; //flags if the finger has moved this.startX = 0; //starting x coordinate this.startY = 0; //starting y coordinate this.hasTouchEventOccured = false; //flag touch event this.el.addEventListener('touchstart', this, false); this.el.addEventListener('mousedown', this, false);}

    2. 事件处理方法“handleEvent”

    上面的“this.el.addEventListener('touchstart', this, false); ”就用到了handleEvent概念来执行事件。

    Tap.prototype.handleEvent = function(e) { switch (e.type) { case 'touchstart':this.start(e);break; case 'touchmove':this.move(e);break; case 'touchend':this.end(e);break; case 'touchcancel':this.cancel(e);break; case 'mousedown':this.start(e);break; case 'mouseup':this.end(e);break; case 'mousemove':this.move(e);break; }};

    3. 触发“tap”

    在前面《触屏touch事件记录》中讲到过,“touchstart”与“touchend”会成对出现,那么就会触发插件中的“end”方法。而end方式就是执行“tap”的关键地方。

    if (!this.moved) { try { evt = new window.CustomEvent('tap', { bubbles: true, cancelable: true }); } catch (e) { evt = document.createEvent('Event'); evt.initEvent('tap', true, true); } e.stopPropagation(); if (!e.target.dispatchEvent(evt)) { e.preventDefault(); }}

    用到了自定义事件的概念,“e.target.dispatchEvent(evt)”这句判断可以执行“e.preventDefault()”,这个地方可以有效的阻止“click”事件的发生,防止“点透”。

    二、Zepto中的touch.js

    Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api。而touch.js是其一个扩展包,用于操作移动端的相关手势事件,兼容IOS、Android与Windows Phone。

    1)Windows Phone指针事件

    在源码中有两套Windows的指针事件,IE10中的MSPointerDown、MSPointerMove、MSPointerUp、MSPointerCancel与IE11中的pointerdown、pointermove、pointerup、pointercancel。

    自 IE11 起,Microsoft 前缀版本的指针事件 API 不再受支持,查看指针事件,指针事件更新。

    2)原理

    与tap.js不同,它是将事件绑定在document中,并且比tap.js更完善,有长按,双击,上下左右划屏等,代码长度也就100多行。

    与tap.js一样也是在touchend中自定义了“tap”事件,从touch.js中的119行开始。与tap.js一样,也有X和Y的偏移距离判断,这里是30以内,tap.js是10以内。

    1 if (deltaX < 30 && deltaY < 30) { 2 tapTimeout = setTimeout(function() { 3 var event = $.Event('tap') 4 event.cancelTouch = cancelAll 5 touch.el.trigger(event) 6 7 if (touch.isDoubleTap) { 8 if (touch.el) touch.el.trigger('doubleTap') 9 touch = {}10 }else {11 touchTimeout = setTimeout(function() {12 touchTimeout = null13 if (touch.el) touch.el.trigger('singleTap')14 touch = {}15 }, 250)16 }17 }, 0);18 }

    上面代码中3、4、5就是在执行“tap”事件了,而7-16行代码是在执行双击或单击。从上面的代码中可以看出,会出现点透,因为并没有阻止“click”事件的执行。

    测试了下在UC中并没有出现点透,但是在微信浏览器中出现了。如下图所示,我点击橙色区域,会触发层下面绑定的“click”事件。

    3)解决方法

    1. 第一种有点暴力,既然没有阻止默认行为,那我就在最后加一个阻止。

    if (deltaX < 30 && deltaY < 30) { //..... e.preventDefault();}

    2. 第二种是在“tap”事件中做个异步,延迟300ms后再触发,这样的话“click”事件已经触发了。

    $('#layer').on('tap', function() { var $this = $(this); setTimeout(function() { $this.hide(); }, 300);});

    三、Hammer.js实现tap

    如果说tap.js是点心,touch.js是简餐,那么hammer.js就是大餐了。2569行代码,兼容各种浏览器,包括移动与PC端。

    浏览器兼容性如下,查看更多兼容性。

    实现的代码中封装了很多的逻辑,最后是将逻辑绑定到了“touchend”中,“touchstart”也绑定了,但是只执行了“touchend”事件。

    关于这个插件的分析,篇幅比较长,可以参考《Hammer.js分析》的几篇文章。

    demo源码下载:

    http://download.csdn.net/download/loneleaf1/9429374

    参考资料:

    http://www.ayqy.net/blog/handleevent%E4%B8%8Eaddeventlistener/   handleEvent与addEventListener

    https://github.com/memoryza/--/blob/master/zepto-tap%E7%82%B9%E9%80%8F.md    zepto tap点击穿透分析

    展开全文
  • Tap口说明和场景

    2021-09-09 23:34:55
    一、 TAP 设备与 VETH 设备 TUN/TAP 设备是一种让用户态程序向内核协议栈注入数据的设备,一个工作在三层,一个工作在二层,使用较多的是 TAP 设备。VETH 设备出现较早,它的作用是反转通讯数据的方向,需要发送的...

    一、 TAP 设备与 VETH 设备

    1. TUN/TAP 设备是一种让用户态程序向内核协议栈注入数据的设备,一个工作在三层,一个工作在二层,使用较多的是 TAP 设备。VETH 设备出现较早,它的作用是反转通讯数据的方向,需要发送的数据会被转换成需要收到的数据重新送入内核网络层进行处理,从而间接的完成数据的注入。
      图 3 .TAP 设备和 VETH 设备工作过程
    2. 如图所示,当备一个 TAP 设被创建时,在 Linux 设备文件目录下将会生成一个对应 char 设备,用户程序可以像打开普通文件一样打开这个文件进行读写。
    3. 当执行 write()操作时,数据进入 TAP 设备,此时对于 Linux 网络层来说,相当于 TAP 设备收到了一包数据,请求内核接受它,如同普通的物理网卡从外界收到一包数据一样,不同的是其实数据来自 Linux 上的一个用户程序。Linux 收到此数据后将根据网络配置进行后续处理,从而完成了用户程序向 Linux 内核网络层注入数据的功能。
    4. 当用户程序执行 read()请求时,相当于向内核查询 TAP 设备上是否有需要被发送出去的数据,有的话取出到用户程序里,完成 TAP 设备的发送数据功能。针对 TAP 设备的一个形象的比喻是:使用 TAP 设备的应用程序相当于另外一台计算机,TAP 设备是本机的一个网卡,他们之间相互连接。应用程序通过 read()/write()操作,和本机网络核心进行通讯。
    5. VETH 设备总是成对出现,送到一端请求发送的数据总是从另一端以请求接收的形式出现。该设备不能被用户程序直接操作,但使用起来比较简单。创建并配置正确后,向 其一端输入数据,VETH 会改变数据的方向并将其送入内核网络核心,完成数据的注入。在另一端能读到此数据。

    二、TAP口的应用场景

    在这里插入图片描述

    1. 这是比较标准的网络流程图,报文从网卡被接收上来,当然这里的网卡是被内核给托管了。 也就是说网卡接收的报文都交给内核进行了处理。内核收到报文后依次走preRoute,LocalIn将报文进行转发或者发到LocalIn。再到用户态被应用程序进行处理。当然,如果是发送报文的话,那就是将报文走LocalOut,然后如果目的地址是本机就转发到本机,否则走路由,然后postRoute.再进入网卡。 这是在PC机上的流程,但是现在的转发设备如交换机,路由器,那就有点不一样了。假如用的是DPDK来接发数据报文。情况就有点不样了。DPDK对网卡进行了托管,内核没有直接管理网卡
      在这里插入图片描述

    2. 如图所以,网卡被DPDK进行管理,DPDK接收和发送网卡的报文。然后报文的转发等处理流程,在用户态进行处理。当然在这种情况下,如果报文不是从本机发出(指的是,不是由本机的内核态发出),而且接收的时候,也不是由本机进行接收(意思是目的地址不是本机)。那么这套流程走起来完全是可以的。但是我们平时用网页管理那个路由器是怎么个实现法,例如,你访问192.168.1.1,然后就会出现一个路由器配置的页面(这里是指家用路由器哈,转发用的不是DPDK。。。。但是打个比方)。很明显,路由器上apache 的流量是从路由器发出,也会接收流量。那这个如何实现。这里就会用到TAP口了。
      在这里插入图片描述

    3. 于是就变成了上图所示,也就是说,当用DPDK把这些报文都收上来,然后对目的IP进行判断(这部分在用户态)。如果是到本机的报文,那么它就交给TAP口,然后TAP收到这个报文后,就会走preRoute -----> LocalIn----->用户态。然后用户态对应的socket就会收到你发的报文里面的内容;如果是Apache产生的信息,那么它当然会调用socket进行发送了,也就是会先进入内核,但是无法直接进入网卡了。现在只能通过TAP口进入用户态,然后在用户态进行转发,再用DPDK发送到对应的网卡。

    4. 使用 TAP 设备的应用程序相当于另外一台计算机,TAP 设备是本机的一个网卡,他们之间相互连接。应用程序通过 read()/write()操作,和本机网络核心进行通讯。

    参考链接:TAP/TUN浅析

    展开全文
  • tun/tap 驱动源码分析

    2021-05-15 16:28:18
    此驱动运行时可设置tun模式和tap模式,tun模式能取到IP数据包,无法获得ARP数据,而tap模式取到的是以太包,可以得到链路层以上的一切数据包。由于项目需要使用tun驱动,而又不想不求甚解,从而阅读了驱动代码,想...

    此驱动运行时可设置tun模式和tap模式,tun模式能取到IP数据包,无法获得ARP数据,而tap模式取到的是以太包,可以得到链路层以上的一切数据包。

    由于项目需要使用tun驱动,而又不想不求甚解,从而阅读了驱动代码,想对此做一些记录,可以使自己理解的更为透彻,虽然代码并不多,但是涉及内核态编程,倘若追根溯源,恐怕需读完Linux协议栈代码了。

    我阅读的代码取自Linux 2.6.34内核源码,路径 drivers\net\tun.c

    驱动入口点,Linux驱动入口点不同Windows平台,入口函数都是DriverEntry,Linux平台需要通过module_init宏指定入口点(当然最终宏也会统一把函数转化成init_module作为入口点),当执行modprobe加载内核模块时由内核调用,module_exit宏指定清理函数,当执行rmmod时由内核调用。另外,通过 MODULE_* 一系列宏记录作者,驱动描述,以及协议信息。

    module_init(tun_init);

    module_exit(tun_cleanup);

    MODULE_DESCRIPTION(DRV_DESCRIPTION);

    MODULE_AUTHOR(DRV_COPYRIGHT);

    MODULE_LICENSE("GPL");

    MODULE_ALIAS_MISCDEV(TUN_MINOR);

    这里指明了驱动程序的基本信息,MODULE_DESCRIPTION声明驱动描述,MODULE_AUTHOR声明作者,在程序开始已有定义。

    #define DRV_DESCRIPTION "Universal TUN/TAP device driver"

    #define DRV_COPYRIGHT "(C) 1999-2004 Max Krasnyansky "

    TUN_MINOR 位于文件 include/linux/miscdevice.h

    #define TUN_MINOR 200

    再来看初始化和清理函数的实现,初始化函数所作工作非常简单,主要功能是建立一个设备节点供用户态进程控制 ret = misc_register(&tun_miscdev); 函数misc_register注册一个字符设备,所注册信息位于tun_miscdev结构。

    static struct miscdevice tun_miscdev = {

    .minor = TUN_MINOR,

    .name = "tun",

    .nodename = "net/tun",

    .fops = &tun_fops,

    };

    调用过后会在/dev下以 nodename 为名创建节点,即/dev/net/tun,用户进程可通过open函数打开并操作驱动,操作驱动函数由 tun_fops 指定。

    static const struct file_operations tun_fops = {

    .owner = THIS_MODULE,

    .llseek = no_llseek,

    .read = do_sync_read,

    .aio_read = tun_chr_aio_read,

    .write = do_sync_write,

    .aio_write = tun_chr_aio_write,

    .poll = tun_chr_poll,

    .unlocked_ioctl = tun_chr_ioctl,

    #ifdef CONFIG_COMPAT

    .compat_ioctl = tun_chr_compat_ioctl,

    #endif

    .open = tun_chr_open,

    .release = tun_chr_close,

    .fasync = tun_chr_fasync

    };

    这里操作都是字符设备操作,用户open设备节点/dev/net/tun时内核调用tun_chr_open回调,这里函数都与用户态操作相对应,用户对设备调用read,write时最终会回调至此,这也是字符设备驱动编程规范。用户态核心操作都在这几个函数当中了。

    现逐条分析每个函数:

    .llseek = no_llseek : no_llseek为内核函数,实现也十分简单,直接返回-ESPIPE,就是说用户态对设备文件调用lseek就会出Illegal seek错。 字符设备,块设备

    .read = do_sync_read : do_sync_read 也是内核函数,直接调用aio_read异步读写函数

    .aio_read = tun_chr_aio_read : tun_chr_aio_read -> tun_do_read -> skb_dequeue 以非阻塞方式从接收队列 tun->socket.sk->sk_receive_queue 取出一个网络包返给用户态。

    .write = do_sync_write : 同aio_read,调用aio_write

    .aio_write = tun_chr_aio_write : tun_chr_aio_write -> tun_get_user -> netif_rx_ni,netif_rx_ni函数为内核函数,最终调用netif_rx返包给TCP/IP协议栈,Linux系统网络数据包都是以sk_buff结构存在,这里函数大部分都是构造此结构。

    .poll = tun_chr_poll : tun_chr_poll 调用 poll_wait 实现poll功能。

    .unlocked_ioctl = tun_chr_ioctl : tun_chr_ioctl -> __tun_chr_ioctl 就是垃圾桶函数ioctl的实现了,所有对驱动程序的操控基本都实现于此函数。函数处理各种不同命令,使用switch-case处理不同命令号,设置硬件地址获取信息等等,关键部分在命令TUNSETIFF,处理在switch之前,这个命令设置基本信息并启动驱动程序的网卡部分。TUNSETIFF 命令最终实现于 tun_set_iff 函数。

    .open = tun_chr_open : tun_chr_open 当用户调用,此函数仅仅分配自定义结构tun_file,存至文件节点私有数据。

    .release = tun_chr_close : tun_chr_close 与 open 操作相反,释放结构体,以及结构体之中的子结构。

    .fasync = tun_chr_fasync

    以上是tun驱动中字符驱动部分,其中省略了细节,如等待队列等内容,跟内核机制有关,我想再另一篇文章中单独总结更好。

    网卡驱动部分

    初始化工作在函数 tun_set_iff 中 alloc_netdev 分配网络设备 -> tun_net_init 初始化网络设备 -> register_netdevice 注册网络设备 其中自定义函数 tun_net_init 关键部分如下:

    switch (tun->flags & TUN_TYPE_MASK) {

    case TUN_TUN_DEV:

    dev->netdev_ops = &tun_netdev_ops;

    /* ... */

    case TUN_TAP_DEV:

    dev->netdev_ops = &tap_netdev_ops;

    /* ... */

    }

    程序查看设置模式,若是tun模式,设置回调函数为 tun_netdev_ops, 若是tap模式,这只回调函数为 tap_netdev_ops。

    先来分析 tun_netdev_ops :

    static const struct net_device_ops tun_netdev_ops = {

    .ndo_uninit = tun_net_uninit,

    .ndo_open = tun_net_open,

    .ndo_stop = tun_net_close,

    .ndo_start_xmit = tun_net_xmit,

    .ndo_change_mtu = tun_net_change_mtu,

    };

    同字符设备驱动的范式,网络驱动也是设置一系列回调函数,当有数据传输时调用相应回调。

    .ndo_open = tun_net_open open 函数调用 netif_start_queue(dev); 通知上层开始接受数据包

    .ndo_stop = tun_net_close close 函数调用 netif_stop_queue(dev); 通知上层停止接受数据包

    .ndo_start_xmit = tun_net_xmit 当有数据包到达时调用 tun_net_xmit 函数,通知网卡发送数据包,函数处理数据包时调用 skb_queue_tail 把数据包压入接收队列 tun->socket.sk->sk_receive_queue

    .ndo_change_mtu = tun_net_change_mtu 改变网卡mtu,控制数据帧大小。

    分析 tap 模式下操作回调 tap_netdev_ops

    static const struct net_device_ops tap_netdev_ops = {

    .ndo_uninit = tun_net_uninit,

    .ndo_open = tun_net_open,

    .ndo_stop = tun_net_close,

    .ndo_start_xmit = tun_net_xmit,

    .ndo_change_mtu = tun_net_change_mtu,

    .ndo_set_multicast_list = tun_net_mclist,

    .ndo_set_mac_address = eth_mac_addr,

    .ndo_validate_addr = eth_validate_addr,

    };

    其中大部分函数都同 tun 模式相同,因为这些函数都无需关心数据包是否含有以太头。而tun_net_mclist仅实现为空函数,eth_mac_addr和eth_validate_addr操作函数直接回调系统默认函数,此模式下并无新回调函数出现。

    虽然Linux系统是用纯C语言写的,但是其中到处充斥着面向对象的思想,分析驱动程序首先理清结构,以及结构对应的方法,对字符设备驱动对象,file存储数据,file_operations回调是其方法;网络设备对象也是如此。

    转载:

    展开全文
  • B5517AF1-98C9-4593-A79D-746BA1A589BA.png (34.05 KB, 下载次数: 13) 2020-8-8 11:38 上传 答:出现这种问题一般是由于触发条件第一次成立和下一次成立之间的间隔,小于signaltap设置的一个窗口的采样深度造成的。...
  • TUN/TAP设备是Linux内核的虚拟网络设备,可以利用TUN设备实现IP数据包的透明传输,TAP设备实现以太网帧的透明传输。利用此项技术,我们就可以在不更改应用程序的基础上使用一些新的传输协议。 TUN/TAP介绍一 TUN/TAP...
  • FPGA零基础学习:Signal tap 逻辑分析仪使用教程 本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入...
  • Linux Host 侧使用的网络元素简介Linux 主要使用以下三种设备模型:Bridge、TAP、VETH、VLAN。Bridge 设备是基于内核实现的二层数据交换设备,其作用类似于现实世界中的二级交换机。TAP 设备是一种工作在二层...
  • TUN/TAP,隧道 - linux

    2021-09-12 21:20:11
    Tun/Tap interface tutorial 什么是IP隧道,Linux怎么实现隧道通信? Linux内核网络设备--TUN. TAP设备 一. concep TUN设备:一种虚拟网络设备,点对点的设备。三层设备,即处理的是IP数据包。不需要物理地址,即...
  • Linux中的TUN/TAP设备

    2021-05-14 20:54:23
    Let‘s say that you configured IPX on the tap0, then whenever kernel sends any IPX packet to tap0, it is passed to the application (VTun for example). Application encrypts, compresses and sends it to...
  • 我有一个物理接口eth0,我想...为此我做:#Create the virtual interfacestunctl -t tap0tunctl -t tap1ifconfig tap0 upifconfig tap1 up#Create the bridgebrctl addbr br0brctl stp br0 offbrctl addif br0 eth0b...
  • then devisename="" echo "there are something taps remain, deleteing again" ovs-vsctl show | grep Port | grep -o "tap..........." > $linktappath vdhcp_clear_ovstap fi } ip link | grep -o "tap............
  • tap/tun接口是什么?

    千次阅读 2021-04-05 17:19:49
    tap/tun 是Linux内核 2.4.x 版本之后使用软件实现的虚拟网络设备,这类接口仅能工作在内核中。 不同于普通的网络接口,没有物理硬件(因此也没有物理线路连接到这类接口)。 可以将tun/tap接口认为是一个普通的网络...
  • 【Signaltap II Logic Analyzer->去掉Enable Signaltap II Logic Analyzer前面的勾勾】 文件输出:【File-Export】可以把Signalt采样到的数据保存为其他格式的文件。其中有csv【适合配合MATLAB使用】,tbl,bmp,...
  • 本文的主要内容是Quartus II下SignalTap仿真的介绍。 设计一个计数器,当计数值为0-8时,OV输出为0,当计数值为9-17时,OV输出1。
  • wb.create_sheet('sheet1',index = 0) tap.column_dimensions['A'].width = 10 tap.column_dimensions['B'].width = 50 tap['A1'] = '游戏名' tap['B1'] = '简介' for name in list_3: tap.append(name) def picture...
  • [导读] 一种matlab调用signaltap采集数据的方法关键词:AD采集MatlabFPGA最近,在利用FPGA采集数据,前端是通过AD采集,然后直接输出给FPGA,需要分析采集到的数据,通常的办法只能在signaltap中,右击信号列表然后...
  • 我们现在做app,使用基于uviewui前端框架的这个ui框架 uviewui是基于vue的,由于前端薄弱,发现了使用中的问题,及时记录下来. ...@tap 和@click的功能是一样的.@tap是zepto.js提供的可以去查一下. .
  • Quartus的SignalTap的使用

    2021-11-22 16:42:00
    一、SignalTap II Logic Analyzer 简介 SignalTap II Logic Analyzer(信号逻辑分析仪),用于FPGA烧录调试时,抓取指定引脚、寄存器、ROM地址等所存储的数据值。它使用的是FPGA的片上资源(逻辑资源、RAM资源),...
  • 网桥+tap+epoll实现交换机 本方案需要两个物理网卡,且每个网卡都需要网桥连接到Tap虚拟网卡,此时可以通过向Tap虚拟网卡进行读写操作达到对物理网卡进行读写操作。 网络拓扑实现过程 网络拓扑实现过程如下: 使用...
  • tap和click的区别

    2021-03-03 09:25:59
    tap和click都是点击事件。 移动端有太多复杂的功能是...而且,tap还有一个特点就是『事件穿透』,就是你执行完绑定的tap事件之后呢,如果下面如果绑定了其他事件或者是本身就存在点子事件的话,也会默认触发。 ...
  • 参考: 【1】vhost(vhost-net): 一种 virtio 高性能的后端驱动实现... ... 1.问题描述 使用普通virtio驱动的tap网卡时,tap虚拟卡是xxxx-vif-agent网络插件创建的...
  • TAP/TUN TAP/TUN 是在 Linux 内核 2.4.x 版本之后完全由软件实现的虚拟网络设备,在功能上 TAP/TUN 和物理网卡没有区别,它们同样都是网络设备,都可以设置 IP 地址,而且都属于 Linux 网络设备管理模块,由 Linux ...
  • } /* * Set an explicit name, if --dev is not tun or tap */ if (strcmp(dev, "tun") && strcmp(dev, "tap")) { strncpynt(ifr.ifr_name, dev, IFNAMSIZ); } /* * Use special ioctl that configures tun/tap ...
  • 本文介绍通过运行xinput命令的方法在Manjaro系统中启用Tap-to-click功能,它可以实现你在Manjaro中的触摸板有点按即点击功能。启用Tap-to-click功能的方法1、以下是在系统终端上启用触摸板(参考:在Deepin Linux ...
  • appium 的tap定位不好使

    2021-06-30 14:21:21
    延时,等页面加载出来,再触发点击事件 第二种方式 考虑到如果页面跳转太快的话,可以加上0.5秒的休眠 os.system("add shell input tap 500 1000" && adb shell sleep 0.5) appium 的安装可以装个软件,也可以在终端...
  • 前面两篇文章已经介绍过 tap/tun 的原理和配置工具。这篇文章通过一个编程示例来深入了解 tap/tun 的程序结构。01 准备工作首先通过 modinfo tun 查看系统内核是否支持 tap/tun 设备驱动。Copy[root@by~]#...
  • tap/tun接口是什么

    2021-08-02 10:08:49
    概述 对tun接口的了解需求主要...下面用到了tunctl和openvpn命令来创建tun/tap接口,但目前推荐使用ip tuntap命令: # ip tuntap help Usage: ip tuntap { add | del | show | list | lst | help } [ dev PHYS_DEV ]
  • Uiew+Tap.swift // // Uiew+Tap.swift // YYYSwiftProductTh // // Created by YYY on 2021/2/13. // import Foundation import UIKit typealias clickViewEventBlock = (_ sender: UIView)->() //作用...
  • mui主动触发tap 事件

    2021-03-16 13:04:56
    mui.trigger(img, 'tap') 第一个参数是元素 第二个是事件类型 第三个可以是个传递的数据 不过是可选参数 var img = $(this).find('img')[0]; mui.trigger(img, 'tap') 关注我 持续更新前端...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 96,187
精华内容 38,474
关键字:

tap